mmu2_protocol_logic.cpp 26 KB

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