66#ifndef TINY_LINK_H
77#define TINY_LINK_H
88
9- #include " TinyProtocol.h"
9+ #include " protocol/MessageType.h"
10+ #include " protocol/Status.h"
11+ #include " protocol/State.h"
12+ #include " protocol/Stats.h"
1013#include " internal/codec/CobsCodec.h"
1114#include " internal/codec/Fletcher16.h"
1215#include " internal/codec/Packet.h"
13- #include " version .h"
16+ #include " Version .h"
1417#include " internal/protocol/AckMessage.h"
1518#include " internal/protocol/LogMessage.h"
1619#include " internal/protocol/HandshakeMessage.h"
@@ -60,7 +63,10 @@ namespace tinylink {
6063 template <typename T, typename Adapter, adapter_check_t <Adapter> = 0 >
6164 class TinyLink {
6265 public:
63- typedef void (*ReceiverCallback)(const T& data);
66+ typedef void (*DataCallback)(const T& data);
67+ typedef void (*LogCallback)(const LogMessage& msg);
68+ typedef void (*HandshakeCallback)(const HandshakeMessage& msg);
69+ typedef void (*AckCallback)(const TinyAck& ack);
6470
6571 private:
6672
@@ -70,10 +76,14 @@ namespace tinylink {
7076 // user data : 3 + sizeof(T) + 2
7177 // internal ACK : 3 + sizeof(TinyAck=2) + 2 = 7
7278 // Handshake : 3 + sizeof(HandshakeMessage=1) + 2 = 6
79+ // Log : 3 + sizeof(LogMessage=32) + 2 = 37
7380 //
74- static const size_t INTERNAL_PAYLOAD_SIZE =
81+ static const size_t INTERNAL_PAYLOAD_ACK_HS =
7582 sizeof (TinyAck) > sizeof (HandshakeMessage)
7683 ? sizeof (TinyAck) : sizeof (HandshakeMessage);
84+ static const size_t INTERNAL_PAYLOAD_SIZE =
85+ INTERNAL_PAYLOAD_ACK_HS > sizeof (LogMessage)
86+ ? INTERNAL_PAYLOAD_ACK_HS : sizeof (LogMessage);
7787 static const size_t MAX_PAYLOAD_SIZE =
7888 sizeof (T) > INTERNAL_PAYLOAD_SIZE
7989 ? sizeof (T) : INTERNAL_PAYLOAD_SIZE;
@@ -91,7 +101,12 @@ namespace tinylink {
91101 Adapter* _hw;
92102 T _data;
93103 TinyStats _stats;
94- ReceiverCallback _onReceive;
104+ DataCallback _onDataReceived;
105+ LogCallback _onLogReceived;
106+ HandshakeCallback _onHandshakeReceived;
107+ AckCallback _onAckReceived;
108+
109+ static TinyLink* _s_instance; // /< For enableAutoUpdate() / autoUpdateISR()
95110
96111 uint8_t _pBuf[PLAIN_SIZE];
97112 uint8_t _rawBuf[MAX_FRAME_ENC];
@@ -210,7 +225,9 @@ namespace tinylink {
210225
211226 // ---- Constructor ----------------------------------------------------
212227 explicit TinyLink (Adapter& hw)
213- : _hw(&hw), _data(), _stats(), _onReceive(nullptr ),
228+ : _hw(&hw), _data(), _stats(),
229+ _onDataReceived(nullptr ), _onLogReceived(nullptr ),
230+ _onHandshakeReceived(nullptr ), _onAckReceived(nullptr ),
214231 _rawIdx(0 ), _currType(0 ), _currSeq(0 ), _nextSeq(0 ),
215232 _hasNew(false ), _lastByte(0 ), _timeout(250 ),
216233 _state(TinyState::WAIT_FOR_SYNC),
@@ -226,8 +243,48 @@ namespace tinylink {
226243
227244 // ---- Configuration --------------------------------------------------
228245
229- void onReceive (ReceiverCallback cb) { _onReceive = cb; }
230- void setTimeout (unsigned long ms) { _timeout = ms; }
246+ /* * @brief Register a callback for incoming user data frames. */
247+ void onDataReceived (DataCallback cb) { _onDataReceived = cb; }
248+ /* * @brief Register a callback for incoming log frames. */
249+ void onLogReceived (LogCallback cb) { _onLogReceived = cb; }
250+ /* * @brief Register a callback for incoming handshake frames. */
251+ void onHandshakeReceived (HandshakeCallback cb) { _onHandshakeReceived = cb; }
252+ /* * @brief Register a callback for incoming ACK frames. */
253+ void onAckReceived (AckCallback cb) { _onAckReceived = cb; }
254+
255+ void setTimeout (unsigned long ms) { _timeout = ms; }
256+
257+ /* *
258+ * @brief Register this instance for interrupt-driven updates.
259+ *
260+ * After calling enableAutoUpdate(), the static function autoUpdateISR()
261+ * can be passed to a hardware timer or UART interrupt attachment routine.
262+ * The ISR will call update() on this TinyLink instance without requiring
263+ * the user to do so in their main loop.
264+ *
265+ * Example (Arduino with TimerOne library):
266+ * @code
267+ * link.enableAutoUpdate();
268+ * Timer1.attachInterrupt(TinyLink<MyData, MyAdapter>::autoUpdateISR, 1000);
269+ * @endcode
270+ *
271+ * @note Only one TinyLink instance per T+Adapter type combination can be
272+ * registered at a time. A second call overwrites the previous one.
273+ */
274+ void enableAutoUpdate () { _s_instance = this ; }
275+
276+ /* *
277+ * @brief Static ISR-compatible update function.
278+ *
279+ * Attach this function to a hardware timer, UART RX interrupt, or any
280+ * platform-specific interrupt source to drive the protocol engine
281+ * automatically.
282+ *
283+ * @see enableAutoUpdate()
284+ */
285+ static void autoUpdateISR () {
286+ if (_s_instance) _s_instance->update ();
287+ }
231288
232289 /* *
233290 * @brief Initiate the link and start the handshake exchange.
@@ -278,7 +335,10 @@ namespace tinylink {
278335 _lastHandshakeSent = 0 ;
279336 _lastSent = 0 ;
280337 _stats.clear ();
281- _onReceive = nullptr ;
338+ _onDataReceived = nullptr ;
339+ _onLogReceived = nullptr ;
340+ _onHandshakeReceived = nullptr ;
341+ _onAckReceived = nullptr ;
282342 }
283343
284344 /* * @brief Clears all protocol statistics counters. */
@@ -400,6 +460,11 @@ namespace tinylink {
400460 if (hsLen == sizeof (HandshakeMessage)) {
401461 handleHandshakeMessage (hsBuf, hsLen,
402462 priorState);
463+ if (_onHandshakeReceived) {
464+ HandshakeMessage hm;
465+ memcpy (&hm, hsBuf, sizeof (hm));
466+ _onHandshakeReceived (hm);
467+ }
403468 } else {
404469 // Malformed HS — restore to idle state.
405470 _state = (priorState == TinyState::CONNECTING ||
@@ -421,12 +486,38 @@ namespace tinylink {
421486 ackBuf, sizeof (ackBuf));
422487 if (ackLen == sizeof (TinyAck)) {
423488 _state = TinyState::WAIT_FOR_SYNC;
489+ if (_onAckReceived) {
490+ TinyAck ack;
491+ memcpy (&ack, ackBuf, sizeof (ack));
492+ _onAckReceived (ack);
493+ }
424494 } else {
425495 _stats.increment (TinyStatus::ERR_CRC);
426496 _state = priorState;
427497 }
428498 continue ;
429499 }
500+
501+ // Log: always dispatched via dedicated callback.
502+ if (wireType ==
503+ message_type_to_wire (MessageType::Log)) {
504+ if (_onLogReceived) {
505+ LogMessage logMsg;
506+ uint8_t rtype = 0 , rseq = 0 ;
507+ size_t logLen = tinylink::packet::unpack (
508+ _pBuf, dLen, &rtype, &rseq,
509+ reinterpret_cast <uint8_t *>(&logMsg),
510+ sizeof (logMsg));
511+ if (logLen == sizeof (LogMessage)) {
512+ _onLogReceived (logMsg);
513+ }
514+ }
515+ _state = (priorState == TinyState::AWAITING_ACK ||
516+ priorState == TinyState::CONNECTING)
517+ ? priorState
518+ : TinyState::WAIT_FOR_SYNC;
519+ continue ;
520+ }
430521 }
431522
432523 // ---- User data frame dispatch ----
@@ -447,10 +538,10 @@ namespace tinylink {
447538 ? TinyState::AWAITING_ACK
448539 : TinyState::WAIT_FOR_SYNC;
449540
450- if (_onReceive ) {
541+ if (_onDataReceived ) {
451542 // Callback fires while state is FRAME_COMPLETE
452543 // so it can observe the dispatch moment.
453- _onReceive (_data);
544+ _onDataReceived (_data);
454545 _state = nextState;
455546 } else {
456547 // Polling: transition first, then signal.
@@ -555,4 +646,10 @@ namespace tinylink {
555646
556647} // namespace tinylink
557648
649+ // ---------------------------------------------------------------------------
650+ // Static member definition (template, so lives in the header)
651+ // ---------------------------------------------------------------------------
652+ template <typename T, typename Adapter, tinylink::adapter_check_t <Adapter> N>
653+ tinylink::TinyLink<T, Adapter, N>* tinylink::TinyLink<T, Adapter, N>::_s_instance = nullptr ;
654+
558655#endif // TINY_LINK_H
0 commit comments