mmu2_protocol_logic.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. #include "mmu2_protocol_logic.h"
  2. #include "mmu2_log.h"
  3. #include "mmu2_fsensor.h"
  4. #include "system_timer.h"
  5. #include <string.h>
  6. namespace MMU2 {
  7. static const uint8_t supportedMmuFWVersion[3] PROGMEM = { 2, 1, 1 };
  8. void ProtocolLogic::CheckAndReportAsyncEvents() {
  9. // even when waiting for a query period, we need to report a change in filament sensor's state
  10. // - it is vital for a precise synchronization of moves of the printer and the MMU
  11. uint8_t fs = (uint8_t)WhereIsFilament();
  12. if (fs != lastFSensor) {
  13. SendAndUpdateFilamentSensor();
  14. }
  15. }
  16. void ProtocolLogic::SendQuery() {
  17. SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
  18. scopeState = ScopeState::QuerySent;
  19. }
  20. void ProtocolLogic::SendFINDAQuery() {
  21. SendMsg(RequestMsg(RequestMsgCodes::Finda, 0));
  22. scopeState = ScopeState::FINDAReqSent;
  23. }
  24. void ProtocolLogic::SendAndUpdateFilamentSensor() {
  25. SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, lastFSensor = (uint8_t)WhereIsFilament()));
  26. scopeState = ScopeState::FilamentSensorStateSent;
  27. }
  28. void ProtocolLogic::SendButton(uint8_t btn) {
  29. SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
  30. scopeState = ScopeState::ButtonSent;
  31. }
  32. void ProtocolLogic::SendVersion(uint8_t stage) {
  33. SendMsg(RequestMsg(RequestMsgCodes::Version, stage));
  34. scopeState = (ScopeState)((uint_fast8_t)ScopeState::S0Sent + stage);
  35. }
  36. void ProtocolLogic::SendReadRegister(uint8_t index, ScopeState nextState) {
  37. SendMsg(RequestMsg(RequestMsgCodes::Read, index));
  38. scopeState = nextState;
  39. }
  40. // searches for "ok\n" in the incoming serial data (that's the usual response of the old MMU FW)
  41. struct OldMMUFWDetector {
  42. uint8_t ok;
  43. inline constexpr OldMMUFWDetector():ok(0) { }
  44. enum class State : uint8_t { MatchingPart, SomethingElse, Matched };
  45. /// @returns true when "ok\n" gets detected
  46. State Detect(uint8_t c){
  47. // consume old MMU FW's data if any -> avoid confusion of protocol decoder
  48. if(ok == 0 && c == 'o'){
  49. ++ok;
  50. return State::MatchingPart;
  51. } else if(ok == 1 && c == 'k'){
  52. ++ok;
  53. return State::MatchingPart;
  54. } else if(ok == 2 && c == '\n'){
  55. return State::Matched;
  56. }
  57. return State::SomethingElse;
  58. }
  59. };
  60. StepStatus ProtocolLogic::ExpectingMessage() {
  61. int bytesConsumed = 0;
  62. int c = -1;
  63. OldMMUFWDetector oldMMUh4x0r; // old MMU FW hacker ;)
  64. // try to consume as many rx bytes as possible (until a message has been completed)
  65. while ((c = uart->read()) >= 0) {
  66. ++bytesConsumed;
  67. RecordReceivedByte(c);
  68. switch (protocol.DecodeResponse(c)) {
  69. case DecodeStatus::MessageCompleted:
  70. rsp = protocol.GetResponseMsg();
  71. LogResponse();
  72. RecordUARTActivity(); // something has happened on the UART, update the timeout record
  73. return MessageReady;
  74. case DecodeStatus::NeedMoreData:
  75. break;
  76. case DecodeStatus::Error:{
  77. // consume old MMU FW's data if any -> avoid confusion of protocol decoder
  78. auto old = oldMMUh4x0r.Detect(c);
  79. if( old == OldMMUFWDetector::State::Matched ){
  80. // hack bad FW version - BEWARE - we silently assume that the first query is an "S0"
  81. // The old MMU FW responds with "ok\n" and we fake the response to a bad FW version at this spot
  82. rsp = ResponseMsg(RequestMsg(RequestMsgCodes::Version, 0), ResponseMsgParamCodes::Accepted, 0);
  83. return MessageReady;
  84. } else if( old == OldMMUFWDetector::State::MatchingPart ){
  85. break;
  86. }
  87. }
  88. [[fallthrough]]; // otherwise
  89. default:
  90. RecordUARTActivity(); // something has happened on the UART, update the timeout record
  91. return ProtocolError;
  92. }
  93. }
  94. if (bytesConsumed != 0) {
  95. RecordUARTActivity(); // something has happened on the UART, update the timeout record
  96. return Processing; // consumed some bytes, but message still not ready
  97. } else if (Elapsed(linkLayerTimeout)) {
  98. return CommunicationTimeout;
  99. }
  100. return Processing;
  101. }
  102. void ProtocolLogic::SendMsg(RequestMsg rq) {
  103. uint8_t txbuff[Protocol::MaxRequestSize()];
  104. uint8_t len = Protocol::EncodeRequest(rq, txbuff);
  105. uart->write(txbuff, len);
  106. LogRequestMsg(txbuff, len);
  107. RecordUARTActivity();
  108. }
  109. void ProtocolLogic::StartSeqRestart() {
  110. retries = maxRetries;
  111. SendVersion(0);
  112. }
  113. void ProtocolLogic::DelayedRestartRestart() {
  114. scopeState = ScopeState::RecoveringProtocolError;
  115. }
  116. void ProtocolLogic::CommandRestart() {
  117. scopeState = ScopeState::CommandSent;
  118. SendMsg(rq);
  119. }
  120. void ProtocolLogic::IdleRestart() {
  121. scopeState = ScopeState::Ready;
  122. }
  123. StepStatus ProtocolLogic::ProcessVersionResponse(uint8_t stage) {
  124. if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != stage) {
  125. // got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
  126. SendVersion(stage);
  127. } else {
  128. mmuFwVersion[stage] = rsp.paramValue;
  129. if (mmuFwVersion[stage] != pgm_read_byte(supportedMmuFWVersion + stage)) {
  130. if (--retries == 0) {
  131. return VersionMismatch;
  132. } else {
  133. SendVersion(stage);
  134. }
  135. } else {
  136. dataTO.Reset(); // got a meaningful response from the MMU, stop data layer timeout tracking
  137. SendVersion(stage + 1);
  138. }
  139. }
  140. return Processing;
  141. }
  142. StepStatus ProtocolLogic::ScopeStep() {
  143. if ( ! ExpectsResponse() ) {
  144. // we are waiting for something
  145. switch (currentScope) {
  146. case Scope::DelayedRestart:
  147. return DelayedRestartWait();
  148. case Scope::Idle:
  149. return IdleWait();
  150. case Scope::Command:
  151. return CommandWait();
  152. case Scope::Stopped:
  153. return StoppedStep();
  154. default:
  155. break;
  156. }
  157. } else {
  158. // we are expecting a message
  159. if (auto expmsg = ExpectingMessage(); expmsg != MessageReady) // this whole statement takes 12B
  160. return expmsg;
  161. // process message
  162. switch (currentScope) {
  163. case Scope::StartSeq:
  164. return StartSeqStep(); // ~270B
  165. case Scope::Idle:
  166. return IdleStep(); // ~300B
  167. case Scope::Command:
  168. return CommandStep(); // ~430B
  169. case Scope::Stopped:
  170. return StoppedStep();
  171. default:
  172. break;
  173. }
  174. }
  175. return Finished;
  176. }
  177. StepStatus ProtocolLogic::StartSeqStep() {
  178. // solve initial handshake
  179. switch (scopeState) {
  180. case ScopeState::S0Sent: // received response to S0 - major
  181. case ScopeState::S1Sent: // received response to S1 - minor
  182. case ScopeState::S2Sent: // received response to S2 - minor
  183. return ProcessVersionResponse((uint8_t)scopeState - (uint8_t)ScopeState::S0Sent);
  184. case ScopeState::S3Sent: // received response to S3 - revision
  185. if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != 3) {
  186. // got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
  187. SendVersion(3);
  188. } else {
  189. mmuFwVersionBuild = rsp.paramValue; // just register the build number
  190. // Start General Interrogation after line up.
  191. // For now we just send the state of the filament sensor, but we may request
  192. // data point states from the MMU as well. TBD in the future, especially with another protocol
  193. SendAndUpdateFilamentSensor();
  194. }
  195. return Processing;
  196. case ScopeState::FilamentSensorStateSent:
  197. SwitchFromStartToIdle();
  198. return Processing; // Returning Finished is not a good idea in case of a fast error recovery
  199. // - it tells the printer, that the command which experienced a protocol error and recovered successfully actually terminated.
  200. // In such a case we must return "Processing" in order to keep the MMU state machine running and prevent the printer from executing next G-codes.
  201. break;
  202. default:
  203. return VersionMismatch;
  204. }
  205. return Finished;
  206. }
  207. StepStatus ProtocolLogic::DelayedRestartWait() {
  208. if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
  209. while (uart->read() != -1)
  210. ; // clear the input buffer
  211. // switch to StartSeq
  212. Start();
  213. }
  214. return Processing;
  215. }
  216. StepStatus ProtocolLogic::CommandWait() {
  217. if (Elapsed(heartBeatPeriod)) {
  218. SendQuery();
  219. } else {
  220. // even when waiting for a query period, we need to report a change in filament sensor's state
  221. // - it is vital for a precise synchronization of moves of the printer and the MMU
  222. CheckAndReportAsyncEvents();
  223. }
  224. return Processing;
  225. }
  226. StepStatus ProtocolLogic::ProcessCommandQueryResponse() {
  227. switch (rsp.paramCode) {
  228. case ResponseMsgParamCodes::Processing:
  229. progressCode = static_cast<ProgressCode>(rsp.paramValue);
  230. errorCode = ErrorCode::OK;
  231. SendAndUpdateFilamentSensor(); // keep on reporting the state of fsensor regularly
  232. return Processing;
  233. case ResponseMsgParamCodes::Error:
  234. // in case of an error the progress code remains as it has been before
  235. errorCode = static_cast<ErrorCode>(rsp.paramValue);
  236. // keep on reporting the state of fsensor regularly even in command error state
  237. // - the MMU checks FINDA and fsensor even while recovering from errors
  238. SendAndUpdateFilamentSensor();
  239. return CommandError;
  240. case ResponseMsgParamCodes::Button:
  241. // The user pushed a button on the MMU. Save it, do what we need to do
  242. // to prepare, then pass it back to the MMU so it can work its magic.
  243. buttonCode = static_cast<Buttons>(rsp.paramValue);
  244. SendAndUpdateFilamentSensor();
  245. return ButtonPushed;
  246. case ResponseMsgParamCodes::Finished:
  247. progressCode = ProgressCode::OK;
  248. scopeState = ScopeState::Ready;
  249. return Finished;
  250. default:
  251. return ProtocolError;
  252. }
  253. }
  254. StepStatus ProtocolLogic::CommandStep() {
  255. switch (scopeState) {
  256. case ScopeState::CommandSent: {
  257. switch (rsp.paramCode) { // the response should be either accepted or rejected
  258. case ResponseMsgParamCodes::Accepted:
  259. progressCode = ProgressCode::OK;
  260. errorCode = ErrorCode::RUNNING;
  261. scopeState = ScopeState::Wait;
  262. break;
  263. case ResponseMsgParamCodes::Rejected:
  264. // rejected - should normally not happen, but report the error up
  265. progressCode = ProgressCode::OK;
  266. errorCode = ErrorCode::PROTOCOL_ERROR;
  267. return CommandRejected;
  268. default:
  269. return ProtocolError;
  270. }
  271. } break;
  272. case ScopeState::QuerySent:
  273. return ProcessCommandQueryResponse();
  274. case ScopeState::FilamentSensorStateSent:
  275. SendFINDAQuery();
  276. return Processing;
  277. case ScopeState::FINDAReqSent:
  278. SendReadRegister(4, ScopeState::StatisticsSent);
  279. scopeState = ScopeState::StatisticsSent;
  280. return Processing;
  281. case ScopeState::StatisticsSent:
  282. scopeState = ScopeState::Wait;
  283. return Processing;
  284. case ScopeState::ButtonSent:
  285. if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
  286. // Button was accepted, decrement the retry.
  287. mmu2.DecrementRetryAttempts();
  288. }
  289. SendAndUpdateFilamentSensor();
  290. break;
  291. default:
  292. return ProtocolError;
  293. }
  294. return Processing;
  295. }
  296. StepStatus ProtocolLogic::IdleWait() {
  297. if (scopeState == ScopeState::Ready) { // check timeout
  298. if (Elapsed(heartBeatPeriod)) {
  299. SendQuery();
  300. return Processing;
  301. }
  302. }
  303. return Finished;
  304. }
  305. StepStatus ProtocolLogic::IdleStep() {
  306. switch (scopeState) {
  307. case ScopeState::QuerySent: // check UART
  308. // If we are accidentally in Idle and we receive something like "T0 P1" - that means the communication dropped out while a command was in progress.
  309. // That causes no issues here, we just need to switch to Command processing and continue there from now on.
  310. // The usual response in this case should be some command and "F" - finished - that confirms we are in an Idle state even on the MMU side.
  311. switch (rsp.request.code) {
  312. case RequestMsgCodes::Cut:
  313. case RequestMsgCodes::Eject:
  314. case RequestMsgCodes::Load:
  315. case RequestMsgCodes::Mode:
  316. case RequestMsgCodes::Tool:
  317. case RequestMsgCodes::Unload:
  318. if (rsp.paramCode != ResponseMsgParamCodes::Finished) {
  319. return SwitchFromIdleToCommand();
  320. }
  321. break;
  322. case RequestMsgCodes::Reset:
  323. // this one is kind of special
  324. // we do not transfer to any "running" command (i.e. we stay in Idle),
  325. // but in case there is an error reported we must make sure it gets propagated
  326. switch (rsp.paramCode) {
  327. case ResponseMsgParamCodes::Button:
  328. // The user pushed a button on the MMU. Save it, do what we need to do
  329. // to prepare, then pass it back to the MMU so it can work its magic.
  330. buttonCode = static_cast<Buttons>(rsp.paramValue);
  331. SendFINDAQuery();
  332. return ButtonPushed;
  333. case ResponseMsgParamCodes::Processing:
  334. // @@TODO we may actually use this branch to report progress of manual operation on the MMU
  335. // The MMU sends e.g. X0 P27 after its restart when the user presses an MMU button to move the Selector
  336. // For now let's behave just like "finished"
  337. case ResponseMsgParamCodes::Finished:
  338. errorCode = ErrorCode::OK;
  339. break;
  340. default:
  341. errorCode = static_cast<ErrorCode>(rsp.paramValue);
  342. SendFINDAQuery(); // continue Idle state without restarting the communication
  343. return CommandError;
  344. }
  345. break;
  346. default:
  347. return ProtocolError;
  348. }
  349. SendFINDAQuery();
  350. return Processing;
  351. case ScopeState::FINDAReqSent:
  352. SendReadRegister(4, ScopeState::StatisticsSent);
  353. scopeState = ScopeState::StatisticsSent;
  354. return Processing;
  355. case ScopeState::StatisticsSent:
  356. failStatistics = rsp.paramValue;
  357. scopeState = ScopeState::Ready;
  358. return Finished;
  359. case ScopeState::ButtonSent:
  360. if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
  361. // Button was accepted, decrement the retry.
  362. mmu2.DecrementRetryAttempts();
  363. }
  364. SendFINDAQuery();
  365. return Processing;
  366. default:
  367. return ProtocolError;
  368. }
  369. // The "return Finished" in this state machine requires a bit of explanation:
  370. // The Idle state either did nothing (still waiting for the heartbeat timeout)
  371. // or just successfully received the answer to Q0, whatever that was.
  372. // In both cases, it is ready to hand over work to a command or something else,
  373. // therefore we are returning Finished (also to exit mmu_loop() and unblock Marlin's loop!).
  374. // If there is no work, we'll end up in the Idle state again
  375. // and we'll send the heartbeat message after the specified timeout.
  376. return Finished;
  377. }
  378. ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
  379. : currentScope(Scope::Stopped)
  380. , scopeState(ScopeState::Ready)
  381. , plannedRq(RequestMsgCodes::unknown, 0)
  382. , lastUARTActivityMs(0)
  383. , dataTO()
  384. , rsp(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0)
  385. , state(State::Stopped)
  386. , lrb(0)
  387. , uart(uart)
  388. , errorCode(ErrorCode::OK)
  389. , progressCode(ProgressCode::OK)
  390. , buttonCode(NoButton)
  391. , lastFSensor((uint8_t)WhereIsFilament())
  392. , findaPressed(false)
  393. , failStatistics(0)
  394. , mmuFwVersion { 0, 0, 0 }
  395. {}
  396. void ProtocolLogic::Start() {
  397. state = State::InitSequence;
  398. currentScope = Scope::StartSeq;
  399. protocol.ResetResponseDecoder(); // important - finished delayed restart relies on this
  400. StartSeqRestart();
  401. }
  402. void ProtocolLogic::Stop() {
  403. state = State::Stopped;
  404. currentScope = Scope::Stopped;
  405. }
  406. void ProtocolLogic::ToolChange(uint8_t slot) {
  407. PlanGenericRequest(RequestMsg(RequestMsgCodes::Tool, slot));
  408. }
  409. void ProtocolLogic::Statistics() {
  410. PlanGenericRequest(RequestMsg(RequestMsgCodes::Version, 3));
  411. }
  412. void ProtocolLogic::UnloadFilament() {
  413. PlanGenericRequest(RequestMsg(RequestMsgCodes::Unload, 0));
  414. }
  415. void ProtocolLogic::LoadFilament(uint8_t slot) {
  416. PlanGenericRequest(RequestMsg(RequestMsgCodes::Load, slot));
  417. }
  418. void ProtocolLogic::EjectFilament(uint8_t slot) {
  419. PlanGenericRequest(RequestMsg(RequestMsgCodes::Eject, slot));
  420. }
  421. void ProtocolLogic::CutFilament(uint8_t slot) {
  422. PlanGenericRequest(RequestMsg(RequestMsgCodes::Cut, slot));
  423. }
  424. void ProtocolLogic::ResetMMU() {
  425. PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, 0));
  426. }
  427. void ProtocolLogic::Button(uint8_t index) {
  428. PlanGenericRequest(RequestMsg(RequestMsgCodes::Button, index));
  429. }
  430. void ProtocolLogic::Home(uint8_t mode) {
  431. PlanGenericRequest(RequestMsg(RequestMsgCodes::Home, mode));
  432. }
  433. void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
  434. plannedRq = rq;
  435. if (!ExpectsResponse()) {
  436. ActivatePlannedRequest();
  437. } // otherwise wait for an empty window to activate the request
  438. }
  439. bool ProtocolLogic::ActivatePlannedRequest() {
  440. if (plannedRq.code == RequestMsgCodes::Button) {
  441. // only issue the button to the MMU and do not restart the state machines
  442. SendButton(plannedRq.value);
  443. plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
  444. return true;
  445. } else if (plannedRq.code != RequestMsgCodes::unknown) {
  446. currentScope = Scope::Command;
  447. SetRequestMsg(plannedRq);
  448. plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
  449. CommandRestart();
  450. return true;
  451. }
  452. return false;
  453. }
  454. StepStatus ProtocolLogic::SwitchFromIdleToCommand() {
  455. currentScope = Scope::Command;
  456. SetRequestMsg(rsp.request);
  457. // we are recovering from a communication drop out, the command is already running
  458. // and we have just received a response to a Q0 message about a command progress
  459. return ProcessCommandQueryResponse();
  460. }
  461. void ProtocolLogic::SwitchToIdle() {
  462. state = State::Running;
  463. currentScope = Scope::Idle;
  464. IdleRestart();
  465. }
  466. void ProtocolLogic::SwitchFromStartToIdle() {
  467. state = State::Running;
  468. currentScope = Scope::Idle;
  469. IdleRestart();
  470. SendQuery(); // force sending Q0 immediately
  471. }
  472. bool ProtocolLogic::Elapsed(uint32_t timeout) const {
  473. return _millis() >= (lastUARTActivityMs + timeout);
  474. }
  475. void ProtocolLogic::RecordUARTActivity() {
  476. lastUARTActivityMs = _millis();
  477. }
  478. void ProtocolLogic::RecordReceivedByte(uint8_t c) {
  479. lastReceivedBytes[lrb] = c;
  480. lrb = (lrb + 1) % lastReceivedBytes.size();
  481. }
  482. constexpr char NibbleToChar(uint8_t c) {
  483. switch (c) {
  484. case 0:
  485. case 1:
  486. case 2:
  487. case 3:
  488. case 4:
  489. case 5:
  490. case 6:
  491. case 7:
  492. case 8:
  493. case 9:
  494. return c + '0';
  495. case 10:
  496. case 11:
  497. case 12:
  498. case 13:
  499. case 14:
  500. case 15:
  501. return (c - 10) + 'a';
  502. default:
  503. return 0;
  504. }
  505. }
  506. void ProtocolLogic::FormatLastReceivedBytes(char *dst) {
  507. for (uint8_t i = 0; i < lastReceivedBytes.size(); ++i) {
  508. uint8_t b = lastReceivedBytes[(lrb - i - 1) % lastReceivedBytes.size()];
  509. dst[i * 3] = NibbleToChar(b >> 4);
  510. dst[i * 3 + 1] = NibbleToChar(b & 0xf);
  511. dst[i * 3 + 2] = ' ';
  512. }
  513. dst[(lastReceivedBytes.size() - 1) * 3 + 2] = 0; // terminate properly
  514. }
  515. void ProtocolLogic::FormatLastResponseMsgAndClearLRB(char *dst) {
  516. *dst++ = '<';
  517. for (uint8_t i = 0; i < lrb; ++i) {
  518. uint8_t b = lastReceivedBytes[i];
  519. if (b < 32)
  520. b = '.';
  521. if (b > 127)
  522. b = '.';
  523. *dst++ = b;
  524. }
  525. *dst = 0; // terminate properly
  526. lrb = 0; // reset the input buffer index in case of a clean message
  527. }
  528. void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size) {
  529. constexpr uint_fast8_t rqs = modules::protocol::Protocol::MaxRequestSize() + 2;
  530. char tmp[rqs] = ">";
  531. static char lastMsg[rqs] = "";
  532. for (uint8_t i = 0; i < size; ++i) {
  533. uint8_t b = txbuff[i];
  534. if (b < 32)
  535. b = '.';
  536. if (b > 127)
  537. b = '.';
  538. tmp[i + 1] = b;
  539. }
  540. tmp[size + 1] = '\n';
  541. tmp[size + 2] = 0;
  542. if (!strncmp_P(tmp, PSTR(">S0*99.\n"), rqs) && !strncmp(lastMsg, tmp, rqs)) {
  543. // @@TODO we skip the repeated request msgs for now
  544. // to avoid spoiling the whole log just with ">S0" messages
  545. // especially when the MMU is not connected.
  546. // We'll lose the ability to see if the printer is actually
  547. // trying to find the MMU, but since it has been reliable in the past
  548. // we can live without it for now.
  549. } else {
  550. MMU2_ECHO_MSG(tmp);
  551. }
  552. memcpy(lastMsg, tmp, rqs);
  553. }
  554. void ProtocolLogic::LogError(const char *reason_P) {
  555. char lrb[lastReceivedBytes.size() * 3];
  556. FormatLastReceivedBytes(lrb);
  557. MMU2_ERROR_MSGRPGM(reason_P);
  558. SERIAL_ECHOPGM(", last bytes: ");
  559. SERIAL_ECHOLN(lrb);
  560. }
  561. void ProtocolLogic::LogResponse() {
  562. char lrb[lastReceivedBytes.size()];
  563. FormatLastResponseMsgAndClearLRB(lrb);
  564. MMU2_ECHO_MSG(lrb);
  565. SERIAL_ECHOLN();
  566. }
  567. StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg_P, StepStatus ss) {
  568. if (dataTO.Record(ss)) {
  569. LogError(msg_P);
  570. return dataTO.InitialCause();
  571. } else {
  572. return Processing; // suppress short drop outs of communication
  573. }
  574. }
  575. StepStatus ProtocolLogic::HandleCommunicationTimeout() {
  576. uart->flush(); // clear the output buffer
  577. protocol.ResetResponseDecoder();
  578. Start();
  579. return SuppressShortDropOuts(PSTR("Communication timeout"), CommunicationTimeout);
  580. }
  581. StepStatus ProtocolLogic::HandleProtocolError() {
  582. uart->flush(); // clear the output buffer
  583. state = State::InitSequence;
  584. currentScope = Scope::DelayedRestart;
  585. DelayedRestartRestart();
  586. return SuppressShortDropOuts(PSTR("Protocol Error"), ProtocolError);
  587. }
  588. StepStatus ProtocolLogic::Step() {
  589. if (!ExpectsResponse()) { // if not waiting for a response, activate a planned request immediately
  590. ActivatePlannedRequest();
  591. }
  592. auto currentStatus = ScopeStep();
  593. switch (currentStatus) {
  594. case Processing:
  595. // we are ok, the state machine continues correctly
  596. break;
  597. case Finished: {
  598. // We are ok, switching to Idle if there is no potential next request planned.
  599. // But the trouble is we must report a finished command if the previous command has just been finished
  600. // i.e. only try to find some planned command if we just finished the Idle cycle
  601. bool previousCommandFinished = currentScope == Scope::Command; // @@TODO this is a nasty hack :(
  602. if (!ActivatePlannedRequest()) { // if nothing is planned, switch to Idle
  603. SwitchToIdle();
  604. } else {
  605. // if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
  606. if (!previousCommandFinished && currentScope == Scope::Command) {
  607. currentStatus = Processing;
  608. }
  609. }
  610. } break;
  611. case CommandRejected:
  612. // we have to repeat it - that's the only thing we can do
  613. // no change in state
  614. // @@TODO wait until Q0 returns command in progress finished, then we can send this one
  615. LogError(PSTR("Command rejected"));
  616. CommandRestart();
  617. break;
  618. case CommandError:
  619. LogError(PSTR("Command Error"));
  620. // we shall probably transfer into the Idle state and await further instructions from the upper layer
  621. // Idle state may solve the problem of keeping up the heart beat running
  622. break;
  623. case VersionMismatch:
  624. LogError(PSTR("Version mismatch"));
  625. Stop(); // cannot continue
  626. break;
  627. case ProtocolError:
  628. currentStatus = HandleProtocolError();
  629. break;
  630. case CommunicationTimeout:
  631. currentStatus = HandleCommunicationTimeout();
  632. break;
  633. default:
  634. break;
  635. }
  636. return currentStatus;
  637. }
  638. uint8_t ProtocolLogic::CommandInProgress() const {
  639. if (currentScope != Scope::Command)
  640. return 0;
  641. return (uint8_t)ReqMsg().code;
  642. }
  643. bool DropOutFilter::Record(StepStatus ss) {
  644. if (occurrences == maxOccurrences) {
  645. cause = ss;
  646. }
  647. --occurrences;
  648. return occurrences == 0;
  649. }
  650. } // namespace MMU2