mmu2_protocol_logic.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. #pragma once
  2. #include <stdint.h>
  3. #include <avr/pgmspace.h>
  4. // #include <array> //@@TODO Don't we have STL for AVR somewhere?
  5. template<typename T, uint8_t N>
  6. class array {
  7. T data[N];
  8. public:
  9. array() = default;
  10. inline constexpr T* begin()const { return data; }
  11. inline constexpr T* end()const { return data + N; }
  12. static constexpr uint8_t size() { return N; }
  13. inline T &operator[](uint8_t i){
  14. return data[i];
  15. }
  16. };
  17. #include "mmu2/error_codes.h"
  18. #include "mmu2/progress_codes.h"
  19. #include "mmu2/buttons.h"
  20. #include "mmu2_protocol.h"
  21. #include "mmu2_serial.h"
  22. /// New MMU2 protocol logic
  23. namespace MMU2 {
  24. using namespace modules::protocol;
  25. class ProtocolLogic;
  26. /// ProtocolLogic stepping statuses
  27. enum StepStatus : uint_fast8_t {
  28. Processing = 0,
  29. MessageReady, ///< a message has been successfully decoded from the received bytes
  30. Finished, ///< Scope finished successfully
  31. Interrupted, ///< received "Finished" message related to a different command than originally issued (most likely the MMU restarted while doing something)
  32. CommunicationTimeout, ///< the MMU failed to respond to a request within a specified time frame
  33. ProtocolError, ///< bytes read from the MMU didn't form a valid response
  34. CommandRejected, ///< the MMU rejected the command due to some other command in progress, may be the user is operating the MMU locally (button commands)
  35. CommandError, ///< the command in progress stopped due to unrecoverable error, user interaction required
  36. VersionMismatch, ///< the MMU reports its firmware version incompatible with our implementation
  37. PrinterError, ///< printer's explicit error - MMU is fine, but the printer was unable to complete the requested operation
  38. CommunicationRecovered,
  39. ButtonPushed, ///< The MMU reported the user pushed one of its three buttons.
  40. };
  41. static constexpr uint32_t linkLayerTimeout = 2000; ///< default link layer communication timeout
  42. static constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; ///< data layer communication timeout
  43. static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of heart beat messages (Q0)
  44. static_assert(heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts");
  45. ///< Filter of short consecutive drop outs which are recovered instantly
  46. class DropOutFilter {
  47. StepStatus cause;
  48. uint8_t occurrences;
  49. public:
  50. static constexpr uint8_t maxOccurrences = 10; // ideally set this to >8 seconds -> 12x heartBeatPeriod
  51. static_assert(maxOccurrences > 1, "we should really silently ignore at least 1 comm drop out if recovered immediately afterwards");
  52. DropOutFilter() = default;
  53. /// @returns true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
  54. bool Record(StepStatus ss);
  55. /// @returns the initial cause which started this drop out event
  56. inline StepStatus InitialCause() const { return cause; }
  57. /// Rearms the object for further processing - basically call this once the MMU responds with something meaningful (e.g. S0 A2)
  58. inline void Reset() { occurrences = maxOccurrences; }
  59. };
  60. /// Logic layer of the MMU vs. printer communication protocol
  61. class ProtocolLogic {
  62. public:
  63. ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance);
  64. /// Start/Enable communication with the MMU
  65. void Start();
  66. /// Stop/Disable communication with the MMU
  67. void Stop();
  68. // Issue commands to the MMU
  69. void ToolChange(uint8_t slot);
  70. void Statistics();
  71. void UnloadFilament();
  72. void LoadFilament(uint8_t slot);
  73. void EjectFilament(uint8_t slot);
  74. void CutFilament(uint8_t slot);
  75. void ResetMMU();
  76. void Button(uint8_t index);
  77. void Home(uint8_t mode);
  78. void ReadRegister(uint8_t address);
  79. void WriteRegister(uint8_t address, uint16_t data);
  80. /// Sets the extra load distance to be reported to the MMU.
  81. /// Beware - this call doesn't send anything to the MMU.
  82. /// The MMU gets the newly set value either by a communication restart or via an explicit WriteRegister call
  83. inline void PlanExtraLoadDistance(uint8_t eld_mm){
  84. initRegs8[0] = eld_mm;
  85. }
  86. /// @returns the currently preset extra load distance
  87. inline uint8_t ExtraLoadDistance()const {
  88. return initRegs8[0];
  89. }
  90. /// Step the state machine
  91. StepStatus Step();
  92. /// @returns the current/latest error code as reported by the MMU
  93. ErrorCode Error() const { return errorCode; }
  94. /// @returns the current/latest process code as reported by the MMU
  95. ProgressCode Progress() const { return progressCode; }
  96. /// @returns the current/latest button code as reported by the MMU
  97. Buttons Button() const { return buttonCode; }
  98. uint8_t CommandInProgress() const;
  99. inline bool Running() const {
  100. return state == State::Running;
  101. }
  102. inline bool FindaPressed() const {
  103. return regs8[0];
  104. }
  105. inline uint16_t FailStatistics() const {
  106. return regs16[0];
  107. }
  108. inline uint8_t MmuFwVersionMajor() const {
  109. return mmuFwVersion[0];
  110. }
  111. inline uint8_t MmuFwVersionMinor() const {
  112. return mmuFwVersion[1];
  113. }
  114. inline uint8_t MmuFwVersionRevision() const {
  115. return mmuFwVersion[2];
  116. }
  117. inline void SetPrinterError(ErrorCode ec){
  118. explicitPrinterError = ec;
  119. }
  120. inline void ClearPrinterError(){
  121. explicitPrinterError = ErrorCode::OK;
  122. }
  123. inline bool IsPrinterError()const {
  124. return explicitPrinterError != ErrorCode::OK;
  125. }
  126. inline ErrorCode PrinterError() const {
  127. return explicitPrinterError;
  128. }
  129. #ifndef UNITTEST
  130. private:
  131. #endif
  132. StepStatus ExpectingMessage();
  133. void SendMsg(RequestMsg rq);
  134. void SendWriteMsg(RequestMsg rq);
  135. void SwitchToIdle();
  136. StepStatus SuppressShortDropOuts(const char *msg_P, StepStatus ss);
  137. StepStatus HandleCommunicationTimeout();
  138. StepStatus HandleProtocolError();
  139. bool Elapsed(uint32_t timeout) const;
  140. void RecordUARTActivity();
  141. void RecordReceivedByte(uint8_t c);
  142. void FormatLastReceivedBytes(char *dst);
  143. void FormatLastResponseMsgAndClearLRB(char *dst);
  144. void LogRequestMsg(const uint8_t *txbuff, uint8_t size);
  145. void LogError(const char *reason_P);
  146. void LogResponse();
  147. StepStatus SwitchFromIdleToCommand();
  148. void SwitchFromStartToIdle();
  149. ErrorCode explicitPrinterError;
  150. enum class State : uint_fast8_t {
  151. Stopped, ///< stopped for whatever reason
  152. InitSequence, ///< initial sequence running
  153. Running ///< normal operation - Idle + Command processing
  154. };
  155. // individual sub-state machines - may be they can be combined into a union since only one is active at once
  156. // or we can blend them into ProtocolLogic at the cost of a less nice code (but hopefully shorter)
  157. // Stopped stopped;
  158. // StartSeq startSeq;
  159. // DelayedRestart delayedRestart;
  160. // Idle idle;
  161. // Command command;
  162. // ProtocolLogicPartBase *currentState; ///< command currently being processed
  163. enum class Scope : uint_fast8_t {
  164. Stopped,
  165. StartSeq,
  166. DelayedRestart,
  167. Idle,
  168. Command
  169. };
  170. Scope currentScope;
  171. // basic scope members
  172. /// @returns true if the state machine is waiting for a response from the MMU
  173. bool ExpectsResponse() const { return ((uint8_t)scopeState & (uint8_t)ScopeState::NotExpectsResponse) == 0; }
  174. /// Common internal states of the derived sub-automata
  175. /// General rule of thumb: *Sent states are waiting for a response from the MMU
  176. enum class ScopeState : uint_fast8_t {
  177. S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another
  178. S1Sent,
  179. S2Sent,
  180. S3Sent,
  181. QuerySent,
  182. CommandSent,
  183. FilamentSensorStateSent,
  184. Reading8bitRegisters,
  185. Reading16bitRegisters,
  186. WritingInitRegisters,
  187. ButtonSent,
  188. ReadRegisterSent, // standalone requests for reading registers - from higher layers
  189. WriteRegisterSent,
  190. // States which do not expect a message - MSb set
  191. NotExpectsResponse = 0x80,
  192. Wait = NotExpectsResponse + 1,
  193. Ready = NotExpectsResponse + 2,
  194. RecoveringProtocolError = NotExpectsResponse + 3,
  195. };
  196. ScopeState scopeState; ///< internal state of the sub-automaton
  197. /// @returns the status of processing of the FINDA query response
  198. /// @param finishedRV returned value in case the message was successfully received and processed
  199. /// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
  200. // StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
  201. /// @returns the status of processing of the statistics query response
  202. /// @param finishedRV returned value in case the message was successfully received and processed
  203. /// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
  204. // StepStatus ProcessStatisticsReqSent(StepStatus finishedRV, State nextState);
  205. /// Called repeatedly while waiting for a query (Q0) period.
  206. /// All event checks to report immediately from the printer to the MMU shall be done in this method.
  207. /// So far, the only such a case is the filament sensor, but there can be more like this in the future.
  208. void CheckAndReportAsyncEvents();
  209. void SendQuery();
  210. void StartReading8bitRegisters();
  211. void ProcessRead8bitRegister();
  212. void StartReading16bitRegisters();
  213. ScopeState ProcessRead16bitRegister(ProtocolLogic::ScopeState stateAtEnd);
  214. void StartWritingInitRegisters();
  215. /// @returns true when all registers have been written into the MMU
  216. bool ProcessWritingInitRegister();
  217. void SendAndUpdateFilamentSensor();
  218. void SendButton(uint8_t btn);
  219. void SendVersion(uint8_t stage);
  220. void SendReadRegister(uint8_t index, ScopeState nextState);
  221. void SendWriteRegister(uint8_t index, uint16_t value, ScopeState nextState);
  222. StepStatus ProcessVersionResponse(uint8_t stage);
  223. /// Top level split - calls the appropriate step based on current scope
  224. StepStatus ScopeStep();
  225. static constexpr uint8_t maxRetries = 6;
  226. uint8_t retries;
  227. void StartSeqRestart();
  228. void DelayedRestartRestart();
  229. void IdleRestart();
  230. void CommandRestart();
  231. StepStatus StartSeqStep();
  232. StepStatus DelayedRestartWait();
  233. StepStatus IdleStep();
  234. StepStatus IdleWait();
  235. StepStatus CommandStep();
  236. StepStatus CommandWait();
  237. StepStatus StoppedStep() { return Processing; }
  238. StepStatus ProcessCommandQueryResponse();
  239. inline void SetRequestMsg(RequestMsg msg) {
  240. rq = msg;
  241. }
  242. inline const RequestMsg &ReqMsg() const { return rq; }
  243. RequestMsg rq = RequestMsg(RequestMsgCodes::unknown, 0);
  244. /// Records the next planned state, "unknown" msg code if no command is planned.
  245. /// This is not intended to be a queue of commands to process, protocol_logic must not queue commands.
  246. /// It exists solely to prevent breaking the Request-Response protocol handshake -
  247. /// - during tests it turned out, that the commands from Marlin are coming in such an asynchronnous way, that
  248. /// we could accidentally send T2 immediately after Q0 without waiting for reception of response to Q0.
  249. ///
  250. /// Beware, if Marlin manages to call PlanGenericCommand multiple times before a response comes,
  251. /// these variables will get overwritten by the last call.
  252. /// However, that should not happen under normal circumstances as Marlin should wait for the Command to finish,
  253. /// which includes all responses (and error recovery if any).
  254. RequestMsg plannedRq;
  255. /// Plan a command to be processed once the immediate response to a sent request arrives
  256. void PlanGenericRequest(RequestMsg rq);
  257. /// Activate the planned state once the immediate response to a sent request arrived
  258. bool ActivatePlannedRequest();
  259. uint32_t lastUARTActivityMs; ///< timestamp - last ms when something occurred on the UART
  260. DropOutFilter dataTO; ///< Filter of short consecutive drop outs which are recovered instantly
  261. ResponseMsg rsp; ///< decoded response message from the MMU protocol
  262. State state; ///< internal state of ProtocolLogic
  263. Protocol protocol; ///< protocol codec
  264. array<uint8_t, 16> lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes
  265. uint8_t lrb;
  266. MMU2Serial *uart; ///< UART interface
  267. ErrorCode errorCode; ///< last received error code from the MMU
  268. ProgressCode progressCode; ///< last received progress code from the MMU
  269. Buttons buttonCode; ///< Last received button from the MMU.
  270. uint8_t lastFSensor; ///< last state of filament sensor
  271. // 8bit registers
  272. static constexpr uint8_t regs8Count = 3;
  273. static_assert(regs8Count > 0); // code is not ready for empty lists of registers
  274. static const uint8_t regs8Addrs[regs8Count] PROGMEM;
  275. uint8_t regs8[regs8Count];
  276. // 16bit registers
  277. static constexpr uint8_t regs16Count = 2;
  278. static_assert(regs16Count > 0); // code is not ready for empty lists of registers
  279. static const uint8_t regs16Addrs[regs16Count] PROGMEM;
  280. uint16_t regs16[regs16Count];
  281. // 8bit init values to be sent to the MMU after line up
  282. static constexpr uint8_t initRegs8Count = 1;
  283. static_assert(initRegs8Count > 0); // code is not ready for empty lists of registers
  284. static const uint8_t initRegs8Addrs[initRegs8Count] PROGMEM;
  285. uint8_t initRegs8[initRegs8Count];
  286. uint8_t regIndex;
  287. uint8_t mmuFwVersion[3];
  288. uint16_t mmuFwVersionBuild;
  289. friend class ProtocolLogicPartBase;
  290. friend class Stopped;
  291. friend class Command;
  292. friend class Idle;
  293. friend class StartSeq;
  294. friend class DelayedRestart;
  295. friend class MMU2;
  296. };
  297. } // namespace MMU2