#pragma once // Serial transport of the station-internal link over the ESP32-C5 native USB Serial/JTAG port. // Framing is the app's Phase 03 one: [AA][55][type][len LE][payload][crc16 LE] with // CRC-16/CCITT-FALSE over type+len+payload (implementation/station-link/README.md, "Serial"). // One writer serialises complete frames; ESP_LOG output travels as LOG frames so the byte // stream never mixes text with a binary frame. #include #include #include #include namespace microbu::serial { /// @brief Byte buffer type for frame payloads. using Bytes = std::vector; /// @brief The type of a frame. Either Link (link protocol), Test (test channel), or Log (ESP_LOG output). enum class FrameType : std::uint8_t { LINK = 0x10, TEST = 0x11, LOG = 0x7F }; constexpr std::size_t maximum_payload = 1536; /// @brief A complete frame with a type and a payload. struct Frame { FrameType type; Bytes payload; }; /// @brief Computes the CRC-16/CCITT-FALSE checksum over a byte range. /// @param data Pointer to the first byte to checksum. /// @param length Number of bytes to checksum. /// @return Checksum value. std::uint16_t crc16_ccitt_false(const std::uint8_t* data, std::size_t length); /// @brief Frames a payload as [AA][55][type][len LE][payload][crc16 LE]. /// @param type Frame type tag. /// @param payload Payload bytes; must not exceed maximum_payload. /// @return Encoded frame bytes. Bytes encode_frame(FrameType type, const Bytes& payload); /// Byte-at-a-time decoder, same state machine as the app's SerialFrameDecoder. class Decoder { public: using Handler = std::function; /// @brief Feeds raw bytes through the frame state machine, invoking the handler per complete frame. /// @param data Pointer to the first byte to feed. /// @param length Number of bytes to feed. /// @param handler Invoked once per complete, CRC-valid frame; may be called zero or more times. void feed(const std::uint8_t* data, std::size_t length, const Handler& handler); /// @return Number of frames rejected for a CRC mismatch so far. std::uint32_t crc_errors() const { return crc_errors_; } /// @return Number of frames decoded successfully so far. std::uint32_t frames() const { return frames_; } private: enum class State { SYNC0, SYNC1, TYPE, LEN_LO, LEN_HI, PAYLOAD, CRC_LO, CRC_HI } state_ = State::SYNC0; std::uint8_t type_ = 0; std::uint16_t length_ = 0, crc_ = 0; Bytes payload_; std::uint32_t crc_errors_ = 0, frames_ = 0; }; struct Counters { std::uint32_t frames = 0, crc_errors = 0, write_failures = 0; }; /// @brief Installs the USB Serial/JTAG driver, starts the reader task and routes ESP_LOG into LOG frames. /// @param on_frame Invoked once per complete, CRC-valid frame. /// @note The handler runs on the reader task; it must only enqueue, never block. void start(const Decoder::Handler& on_frame); /// @brief Writes one complete frame (blocking, mutex-protected). Safe from any task. /// @param type Frame type tag. /// @param payload Payload bytes; must not exceed maximum_payload. /// @return false on a full payload, missing driver, lock timeout, or write failure. bool write(FrameType type, const Bytes& payload); /// @return Current frame/error counters. Counters counters(); } // namespace microbu::serial