mmu2_protocol_logic.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #pragma once
  2. #include <stdint.h>
  3. // #include <array> //@@TODO Don't we have STL for AVR somewhere?
  4. template<typename T, uint8_t N>
  5. class array {
  6. T data[N];
  7. public:
  8. array() = default;
  9. inline constexpr T* begin()const { return data; }
  10. inline constexpr T* end()const { return data + N; }
  11. constexpr uint8_t size()const { return N; }
  12. inline T &operator[](uint8_t i){
  13. return data[i];
  14. }
  15. };
  16. #include "mmu2/error_codes.h"
  17. #include "mmu2/progress_codes.h"
  18. #include "mmu2/buttons.h"
  19. #include "mmu2_protocol.h"
  20. #include "mmu2_serial.h"
  21. /// New MMU2 protocol logic
  22. namespace MMU2 {
  23. using namespace modules::protocol;
  24. class ProtocolLogic;
  25. /// ProtocolLogic stepping statuses
  26. enum StepStatus : uint_fast8_t {
  27. Processing = 0,
  28. MessageReady, ///< a message has been successfully decoded from the received bytes
  29. Finished,
  30. CommunicationTimeout, ///< the MMU failed to respond to a request within a specified time frame
  31. ProtocolError, ///< bytes read from the MMU didn't form a valid response
  32. CommandRejected, ///< the MMU rejected the command due to some other command in progress, may be the user is operating the MMU locally (button commands)
  33. CommandError, ///< the command in progress stopped due to unrecoverable error, user interaction required
  34. VersionMismatch, ///< the MMU reports its firmware version incompatible with our implementation
  35. CommunicationRecovered,
  36. ButtonPushed, ///< The MMU reported the user pushed one of its three buttons.
  37. };
  38. static constexpr uint32_t linkLayerTimeout = 2000; ///< default link layer communication timeout
  39. static constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; ///< data layer communication timeout
  40. static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of heart beat messages (Q0)
  41. static_assert( heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts");
  42. /// Base class for sub-automata of the ProtocolLogic class.
  43. /// Their operation should never block (wait inside).
  44. class ProtocolLogicPartBase {
  45. public:
  46. inline ProtocolLogicPartBase(ProtocolLogic *logic)
  47. : logic(logic)
  48. , state(State::Ready) {}
  49. /// Restarts the sub-automaton
  50. virtual void Restart() = 0;
  51. /// Makes one step in the sub-automaton
  52. /// @returns StepStatus
  53. virtual StepStatus Step() = 0;
  54. /// @returns true if the state machine is waiting for a response from the MMU
  55. bool ExpectsResponse()const { return state != State::Ready && state != State::Wait; }
  56. protected:
  57. ProtocolLogic *logic; ///< pointer to parent ProtocolLogic layer
  58. friend class ProtocolLogic;
  59. /// Common internal states of the derived sub-automata
  60. /// General rule of thumb: *Sent states are waiting for a response from the MMU
  61. enum class State : uint_fast8_t {
  62. Ready,
  63. Wait,
  64. S0Sent,
  65. S1Sent,
  66. S2Sent,
  67. QuerySent,
  68. CommandSent,
  69. FilamentSensorStateSent,
  70. FINDAReqSent,
  71. ButtonSent,
  72. ContinueFromIdle
  73. };
  74. State state; ///< internal state of the sub-automaton
  75. /// @returns the status of processing of the FINDA query response
  76. /// @param finishedRV returned value in case the message was successfully received and processed
  77. /// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
  78. StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
  79. /// Called repeatedly while waiting for a query (Q0) period.
  80. /// All event checks to report immediately from the printer to the MMU shall be done in this method.
  81. /// So far, the only such a case is the filament sensor, but there can be more like this in the future.
  82. void CheckAndReportAsyncEvents();
  83. void SendQuery();
  84. void SendFINDAQuery();
  85. void SendAndUpdateFilamentSensor();
  86. void SendButton(uint8_t btn);
  87. };
  88. /// Starting sequence of the communication with the MMU.
  89. /// The printer shall ask for MMU's version numbers.
  90. /// If everything goes well and the MMU's version is good enough,
  91. /// the ProtocolLogic layer may continue talking to the MMU
  92. class StartSeq : public ProtocolLogicPartBase {
  93. public:
  94. inline StartSeq(ProtocolLogic *logic)
  95. : ProtocolLogicPartBase(logic) {}
  96. void Restart() override;
  97. StepStatus Step() override;
  98. };
  99. /// A command and its lifecycle.
  100. /// CommandSent:
  101. /// - the command was placed into the UART TX buffer, awaiting response from the MMU
  102. /// - if the MMU confirms the command, we'll wait for it to finish
  103. /// - if the MMU refuses the command, we report an error (should normally not happen unless someone is hacking the communication without waiting for the previous command to finish)
  104. /// Wait:
  105. /// - waiting for the MMU to process the command - may take several seconds, for example Tool change operation
  106. /// - meawhile, every 300ms we send a Q0 query to obtain the current state of the command being processed
  107. /// - as soon as we receive a response to Q0 from the MMU, we process it in the next state
  108. /// QuerySent - check the reply from the MMU - can be any of the following:
  109. /// - Processing: the MMU is still working
  110. /// - Error: the command failed on the MMU, we'll have the exact error report in the response message
  111. /// - Finished: the MMU finished the command successfully, another command may be issued now
  112. class Command : public ProtocolLogicPartBase {
  113. public:
  114. inline Command(ProtocolLogic *logic)
  115. : ProtocolLogicPartBase(logic)
  116. , rq(RequestMsgCodes::unknown, 0) {}
  117. void Restart() override;
  118. StepStatus Step() override;
  119. inline void SetRequestMsg(RequestMsg msg) {
  120. rq = msg;
  121. }
  122. void ContinueFromIdle(){
  123. state = State::ContinueFromIdle;
  124. }
  125. inline const RequestMsg &ReqMsg()const { return rq; }
  126. private:
  127. RequestMsg rq;
  128. };
  129. /// Idle state - we have no command for the MMU, so we are only regularly querying its state with Q0 messages.
  130. /// The idle state can be interrupted any time to issue a command into the MMU
  131. class Idle : public ProtocolLogicPartBase {
  132. public:
  133. inline Idle(ProtocolLogic *logic)
  134. : ProtocolLogicPartBase(logic) {}
  135. void Restart() override;
  136. StepStatus Step() override;
  137. };
  138. /// The communication with the MMU is stopped/disabled (for whatever reason).
  139. /// Nothing is being put onto the UART.
  140. class Stopped : public ProtocolLogicPartBase {
  141. public:
  142. inline Stopped(ProtocolLogic *logic)
  143. : ProtocolLogicPartBase(logic) {}
  144. void Restart() override {}
  145. StepStatus Step() override { return Processing; }
  146. };
  147. ///< Filter of short consecutive drop outs which are recovered instantly
  148. class DropOutFilter {
  149. StepStatus cause;
  150. uint8_t occurrences;
  151. public:
  152. static constexpr uint8_t maxOccurrences = 10; // ideally set this to >8 seconds -> 12x heartBeatPeriod
  153. static_assert (maxOccurrences > 1, "we should really silently ignore at least 1 comm drop out if recovered immediately afterwards");
  154. DropOutFilter() = default;
  155. /// @returns true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
  156. bool Record(StepStatus ss);
  157. /// @returns the initial cause which started this drop out event
  158. inline StepStatus InitialCause()const { return cause; }
  159. /// Rearms the object for further processing - basically call this once the MMU responds with something meaningful (e.g. S0 A2)
  160. inline void Reset(){ occurrences = maxOccurrences; }
  161. };
  162. /// Logic layer of the MMU vs. printer communication protocol
  163. class ProtocolLogic {
  164. public:
  165. ProtocolLogic(MMU2Serial *uart);
  166. /// Start/Enable communication with the MMU
  167. void Start();
  168. /// Stop/Disable communication with the MMU
  169. void Stop();
  170. // Issue commands to the MMU
  171. void ToolChange(uint8_t slot);
  172. void UnloadFilament();
  173. void LoadFilament(uint8_t slot);
  174. void EjectFilament(uint8_t slot);
  175. void CutFilament(uint8_t slot);
  176. void ResetMMU();
  177. void Button(uint8_t index);
  178. void Home(uint8_t mode);
  179. /// Step the state machine
  180. StepStatus Step();
  181. /// @returns the current/latest error code as reported by the MMU
  182. ErrorCode Error() const { return errorCode; }
  183. /// @returns the current/latest process code as reported by the MMU
  184. ProgressCode Progress() const { return progressCode; }
  185. /// @returns the current/latest button code as reported by the MMU
  186. Buttons Button() const { return buttonCode; }
  187. uint8_t CommandInProgress()const;
  188. inline bool Running()const {
  189. return state == State::Running;
  190. }
  191. inline bool FindaPressed() const {
  192. return findaPressed;
  193. }
  194. inline uint8_t MmuFwVersionMajor() const {
  195. return mmuFwVersionMajor;
  196. }
  197. inline uint8_t MmuFwVersionMinor() const {
  198. return mmuFwVersionMinor;
  199. }
  200. inline uint8_t MmuFwVersionBuild() const {
  201. return mmuFwVersionBuild;
  202. }
  203. #ifndef UNITTEST
  204. private:
  205. #endif
  206. StepStatus ProcessUARTByte(uint8_t c);
  207. StepStatus ExpectingMessage(uint32_t timeout);
  208. void SendMsg(RequestMsg rq);
  209. void SwitchToIdle();
  210. void HandleCommunicationTimeout();
  211. StepStatus HandleCommError(const char *msg, StepStatus ss);
  212. bool Elapsed(uint32_t timeout) const;
  213. void RecordUARTActivity();
  214. void RecordReceivedByte(uint8_t c);
  215. void FormatLastReceivedBytes(char *dst);
  216. void FormatLastResponseMsgAndClearLRB(char *dst);
  217. void LogRequestMsg(const uint8_t *txbuff, uint8_t size);
  218. void LogError(const char *reason);
  219. void LogResponse();
  220. void SwitchFromIdleToCommand();
  221. enum class State : uint_fast8_t {
  222. Stopped, ///< stopped for whatever reason
  223. InitSequence, ///< initial sequence running
  224. Running ///< normal operation - Idle + Command processing
  225. };
  226. // individual sub-state machines - may be they can be combined into a union since only one is active at once
  227. Stopped stopped;
  228. StartSeq startSeq;
  229. Idle idle;
  230. Command command;
  231. ProtocolLogicPartBase *currentState; ///< command currently being processed
  232. /// Records the next planned state, "unknown" msg code if no command is planned.
  233. /// This is not intended to be a queue of commands to process, protocol_logic must not queue commands.
  234. /// It exists solely to prevent breaking the Request-Response protocol handshake -
  235. /// - during tests it turned out, that the commands from Marlin are coming in such an asynchronnous way, that
  236. /// we could accidentally send T2 immediately after Q0 without waiting for reception of response to Q0.
  237. ///
  238. /// Beware, if Marlin manages to call PlanGenericCommand multiple times before a response comes,
  239. /// these variables will get overwritten by the last call.
  240. /// However, that should not happen under normal circumstances as Marlin should wait for the Command to finish,
  241. /// which includes all responses (and error recovery if any).
  242. RequestMsg plannedRq;
  243. /// Plan a command to be processed once the immediate response to a sent request arrives
  244. void PlanGenericRequest(RequestMsg rq);
  245. /// Activate the planned state once the immediate response to a sent request arrived
  246. bool ActivatePlannedRequest();
  247. uint32_t lastUARTActivityMs; ///< timestamp - last ms when something occurred on the UART
  248. DropOutFilter dataTO; ///< Filter of short consecutive drop outs which are recovered instantly
  249. ResponseMsg rsp; ///< decoded response message from the MMU protocol
  250. State state; ///< internal state of ProtocolLogic
  251. Protocol protocol; ///< protocol codec
  252. array<uint8_t, 16> lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes
  253. uint8_t lrb;
  254. MMU2Serial *uart; ///< UART interface
  255. ErrorCode errorCode; ///< last received error code from the MMU
  256. ProgressCode progressCode; ///< last received progress code from the MMU
  257. Buttons buttonCode; ///< Last received button from the MMU.
  258. uint8_t lastFSensor; ///< last state of filament sensor
  259. bool findaPressed;
  260. uint8_t mmuFwVersionMajor, mmuFwVersionMinor;
  261. uint8_t mmuFwVersionBuild;
  262. friend class ProtocolLogicPartBase;
  263. friend class Stopped;
  264. friend class Command;
  265. friend class Idle;
  266. friend class StartSeq;
  267. friend class MMU2;
  268. };
  269. } // namespace MMU2