mmu2_protocol_logic.cpp 27 KB

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