mmu2_protocol_logic.cpp 29 KB

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