| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 | /// @file protocol.h#pragma once#include <stdint.h>#include "mmu2_crc.h"namespace modules {/// @brief The MMU communication protocol implementation and related stuff.////// See description of the new protocol in the MMU 2021 docnamespace protocol {/// Definition of request message codesenum class RequestMsgCodes : uint8_t {    unknown = 0,    Query = 'Q',    Tool = 'T',    Load = 'L',    Mode = 'M',    Unload = 'U',    Reset = 'X',    Finda = 'P',    Version = 'S',    Button = 'B',    Eject = 'E',    Write = 'W',    Cut = 'K',    FilamentType = 'F',    FilamentSensor = 'f',    Home = 'H',    Read = 'R'};/// Definition of response message parameter codesenum class ResponseMsgParamCodes : uint8_t {    unknown = 0,    Processing = 'P',    Error = 'E',    Finished = 'F',    Accepted = 'A',    Rejected = 'R',    Button = 'B' // the MMU registered a button press and is sending it to the printer for processing};/// A request message - requests are being sent by the printer into the MMU.struct RequestMsg {    RequestMsgCodes code; ///< code of the request message    uint8_t value; ///< value of the request message or address of variable to read/write    uint16_t value2; ///< in case or write messages - value to be written into the register    /// CRC8 check - please note we abuse this byte for CRC of ResponseMsgs as well.    /// The crc8 byte itself is not added into the CRC computation (obviously ;) )    /// Beware - adding any members of this data structure may need changing the way CRC is being computed!    uint8_t crc8;    constexpr uint8_t ComputeCRC8() const {        uint8_t crc = 0;        crc = modules::crc::CRC8::CCITT_updateCX(0, (uint8_t)code);        crc = modules::crc::CRC8::CCITT_updateCX(crc, value);        crc = modules::crc::CRC8::CCITT_updateW(crc, value2);        return crc;    }    /// @param code of the request message    /// @param value of the request message    inline constexpr RequestMsg(RequestMsgCodes code, uint8_t value)        : code(code)        , value(value)        , value2(0)        , crc8(ComputeCRC8()) {    }    /// Intended for write requests    /// @param code of the request message ('W')    /// @param address of the register    /// @param value to write into the register    inline constexpr RequestMsg(RequestMsgCodes code, uint8_t address, uint16_t value)        : code(code)        , value(address)        , value2(value)        , crc8(ComputeCRC8()) {    }    constexpr uint8_t CRC() const { return crc8; }};/// A response message - responses are being sent from the MMU into the printer as a response to a request message.struct ResponseMsg {    RequestMsg request; ///< response is always preceeded by the request message    ResponseMsgParamCodes paramCode; ///< code of the parameter    uint16_t paramValue; ///< value of the parameter    constexpr uint8_t ComputeCRC8() const {        uint8_t crc = request.ComputeCRC8();        crc = modules::crc::CRC8::CCITT_updateCX(crc, (uint8_t)paramCode);        crc = modules::crc::CRC8::CCITT_updateW(crc, paramValue);        return crc;    }    /// @param request the source request message this response is a reply to    /// @param paramCode code of the parameter    /// @param paramValue value of the parameter    inline constexpr ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint16_t paramValue)        : request(request)        , paramCode(paramCode)        , paramValue(paramValue) {        this->request.crc8 = ComputeCRC8();    }    constexpr uint8_t CRC() const { return request.crc8; }};/// Combined commandStatus and its value into one data structure (optimization purposes)struct ResponseCommandStatus {    ResponseMsgParamCodes code;    uint16_t value;    inline constexpr ResponseCommandStatus(ResponseMsgParamCodes code, uint16_t value)        : code(code)        , value(value) {}};/// Message decoding return valuesenum class DecodeStatus : uint_fast8_t {    MessageCompleted, ///< message completed and successfully lexed    NeedMoreData, ///< message incomplete yet, waiting for another byte to come    Error, ///< input character broke message decoding};/// Protocol class is responsible for creating/decoding messages in Rx/Tx buffer////// Beware - in the decoding more, it is meant to be a statefull instance which works through public methods/// processing one input byte per call.class Protocol {public:    inline Protocol()        : rqState(RequestStates::Code)        , requestMsg(RequestMsgCodes::unknown, 0)        , rspState(ResponseStates::RequestCode)        , responseMsg(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0) {    }    /// Takes the input byte c and steps one step through the state machine    /// @returns state of the message being decoded    DecodeStatus DecodeRequest(uint8_t c);    /// Decodes response message in rxbuff    /// @returns decoded response message structure    DecodeStatus DecodeResponse(uint8_t c);    /// Encodes request message msg into txbuff memory    /// It is expected the txbuff is large enough to fit the message    /// @returns number of bytes written into txbuff    static uint8_t EncodeRequest(const RequestMsg &msg, uint8_t *txbuff);    /// Encodes Write request message msg into txbuff memory    /// It is expected the txbuff is large enough to fit the message    /// @returns number of bytes written into txbuff    static uint8_t EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff);    /// @returns the maximum byte length necessary to encode a request message    /// Beneficial in case of pre-allocating a buffer for enconding a RequestMsg.    static constexpr uint8_t MaxRequestSize() { return 13; }    /// @returns the maximum byte length necessary to encode a response message    /// Beneficial in case of pre-allocating a buffer for enconding a ResponseMsg.    static constexpr uint8_t MaxResponseSize() { return 14; }    /// Encode generic response Command Accepted or Rejected    /// @param msg source request message for this response    /// @param ar code of response parameter    /// @param txbuff where to format the message    /// @returns number of bytes written into txbuff    static uint8_t EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff);    /// Encode response to Read FINDA query    /// @param msg source request message for this response    /// @param findaValue 1/0 (on/off) status of FINDA    /// @param txbuff where to format the message    /// @returns number of bytes written into txbuff    static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff);    /// Encode response to Query operation status    /// @param msg source request message for this response    /// @param code status of operation (Processing, Error, Finished)    /// @param value related to status of operation(e.g. error code or progress)    /// @param txbuff where to format the message    /// @returns number of bytes written into txbuff    static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseCommandStatus rcs, uint8_t *txbuff);    /// Encode response to Read query    /// @param msg source request message for this response    /// @param accepted true if the read query was accepted    /// @param value2 variable value    /// @param txbuff where to format the message    /// @returns number of bytes written into txbuff    static uint8_t EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff);    /// @returns the most recently lexed request message    inline const RequestMsg GetRequestMsg() const { return requestMsg; }    /// @returns the most recently lexed response message    inline const ResponseMsg GetResponseMsg() const { return responseMsg; }    /// resets the internal request decoding state (typically after an error)    void ResetRequestDecoder() {        rqState = RequestStates::Code;    }    /// resets the internal response decoding state (typically after an error)    void ResetResponseDecoder() {        rspState = ResponseStates::RequestCode;    }#ifndef UNITTESTprivate:#endif    enum class RequestStates : uint8_t {        Code, ///< starting state - expects message code        Value, ///< expecting code value        Address, ///< expecting address for Write command        WriteValue, ///< value to be written (Write command)        CRC, ///< CRC        Error ///< automaton in error state    };    RequestStates rqState;    RequestMsg requestMsg;    enum class ResponseStates : uint8_t {        RequestCode, ///< starting state - expects message code        RequestValue, ///< expecting code value        ParamCode, ///< expecting param code        ParamValue, ///< expecting param value        CRC, ///< expecting CRC value        Error ///< automaton in error state    };    ResponseStates rspState;    ResponseMsg responseMsg;    static constexpr bool IsNewLine(uint8_t c) {        return c == '\n' || c == '\r';    }    static constexpr bool IsDigit(uint8_t c) {        return c >= '0' && c <= '9';    }    static constexpr bool IsCRCSeparator(uint8_t c) {        return c == '*';    }    static constexpr bool IsHexDigit(uint8_t c) {        return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');    }    static constexpr uint8_t Char2Nibble(uint8_t c) {        switch (c) {        case '0':        case '1':        case '2':        case '3':        case '4':        case '5':        case '6':        case '7':        case '8':        case '9':            return c - '0';        case 'a':        case 'b':        case 'c':        case 'd':        case 'e':        case 'f':            return c - 'a' + 10;        default:            return 0;        }    }    static constexpr uint8_t Nibble2Char(uint8_t n) {        switch (n) {        case 0:        case 1:        case 2:        case 3:        case 4:        case 5:        case 6:        case 7:        case 8:        case 9:            return n + '0';        case 0xa:        case 0xb:        case 0xc:        case 0xd:        case 0xe:        case 0xf:            return n - 10 + 'a';        default:            return 0;        }    }    /// @returns number of characters written    static uint8_t UInt8ToHex(uint8_t value, uint8_t *dst);    /// @returns number of characters written    static uint8_t UInt16ToHex(uint16_t value, uint8_t *dst);    static uint8_t BeginEncodeRequest(const RequestMsg &msg, uint8_t *dst);    static uint8_t AppendCRC(uint8_t crc, uint8_t *dst);};} // namespace protocol} // namespace modulesnamespace mp = modules::protocol;
 |