mmu2_protocol_logic.cpp 26 KB

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