mmu2_protocol_logic.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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. StepStatus ProtocolLogicPartBase::ProcessFINDAReqSent(StepStatus finishedRV, State nextState){
  8. auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
  9. if (expmsg != MessageReady)
  10. return expmsg;
  11. logic->findaPressed = logic->rsp.paramValue;
  12. state = nextState;
  13. return finishedRV;
  14. }
  15. void ProtocolLogicPartBase::CheckAndReportAsyncEvents(){
  16. // even when waiting for a query period, we need to report a change in filament sensor's state
  17. // - it is vital for a precise synchronization of moves of the printer and the MMU
  18. uint8_t fs = (uint8_t)WhereIsFilament();
  19. if( fs != logic->lastFSensor ){
  20. SendAndUpdateFilamentSensor();
  21. }
  22. }
  23. void ProtocolLogicPartBase::SendQuery(){
  24. logic->SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
  25. state = State::QuerySent;
  26. }
  27. void ProtocolLogicPartBase::SendFINDAQuery(){
  28. logic->SendMsg(RequestMsg(RequestMsgCodes::Finda, 0 ) );
  29. state = State::FINDAReqSent;
  30. }
  31. void ProtocolLogicPartBase::SendAndUpdateFilamentSensor(){
  32. logic->SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, logic->lastFSensor = (uint8_t)WhereIsFilament() ) );
  33. state = State::FilamentSensorStateSent;
  34. }
  35. void ProtocolLogicPartBase::SendButton(uint8_t btn){
  36. logic->SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
  37. state = State::ButtonSent;
  38. }
  39. StepStatus ProtocolLogic::ProcessUARTByte(uint8_t c) {
  40. switch (protocol.DecodeResponse(c)) {
  41. case DecodeStatus::MessageCompleted:
  42. // @@TODO reset direction of communication
  43. return MessageReady;
  44. case DecodeStatus::NeedMoreData:
  45. return Processing;
  46. case DecodeStatus::Error:
  47. default:
  48. return ProtocolError;
  49. }
  50. }
  51. StepStatus ProtocolLogic::ExpectingMessage(uint32_t timeout) {
  52. int bytesConsumed = 0;
  53. int c = -1;
  54. // try to consume as many rx bytes as possible (until a message has been completed)
  55. while((c = uart->read()) >= 0){
  56. ++bytesConsumed;
  57. RecordReceivedByte(c);
  58. switch (protocol.DecodeResponse(c)) {
  59. case DecodeStatus::MessageCompleted:
  60. rsp = protocol.GetResponseMsg();
  61. LogResponse();
  62. // @@TODO reset direction of communication
  63. RecordUARTActivity(); // something has happened on the UART, update the timeout record
  64. return MessageReady;
  65. case DecodeStatus::NeedMoreData:
  66. break;
  67. case DecodeStatus::Error:
  68. default:
  69. RecordUARTActivity(); // something has happened on the UART, update the timeout record
  70. return ProtocolError;
  71. }
  72. }
  73. if( bytesConsumed != 0 ){
  74. RecordUARTActivity(); // something has happened on the UART, update the timeout record
  75. return Processing; // consumed some bytes, but message still not ready
  76. } else if (Elapsed(timeout)) {
  77. return CommunicationTimeout;
  78. }
  79. return Processing;
  80. }
  81. void ProtocolLogic::SendMsg(RequestMsg rq) {
  82. uint8_t txbuff[Protocol::MaxRequestSize()];
  83. uint8_t len = Protocol::EncodeRequest(rq, txbuff);
  84. uart->write(txbuff, len);
  85. LogRequestMsg(txbuff, len);
  86. RecordUARTActivity();
  87. }
  88. void StartSeq::Restart() {
  89. state = State::S0Sent;
  90. logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 0));
  91. }
  92. StepStatus StartSeq::Step() {
  93. auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
  94. if (expmsg != MessageReady)
  95. return expmsg;
  96. // solve initial handshake
  97. switch (state) {
  98. case State::S0Sent: // received response to S0 - major
  99. if (logic->rsp.paramValue != 2) {
  100. return VersionMismatch;
  101. }
  102. logic->dataTO.Reset(); // got meaningful response from the MMU, stop data layer timeout tracking
  103. logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 1));
  104. state = State::S1Sent;
  105. break;
  106. case State::S1Sent: // received response to S1 - minor
  107. if (logic->rsp.paramValue != 0) {
  108. return VersionMismatch;
  109. }
  110. logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 2));
  111. state = State::S2Sent;
  112. break;
  113. case State::S2Sent: // received response to S2 - revision
  114. if (logic->rsp.paramValue != 0) {
  115. return VersionMismatch;
  116. }
  117. // Start General Interrogation after line up.
  118. // For now we just send the state of the filament sensor, but we may request
  119. // data point states from the MMU as well. TBD in the future, especially with another protocol
  120. SendAndUpdateFilamentSensor();
  121. break;
  122. case State::FilamentSensorStateSent:
  123. state = State::Ready;
  124. return Finished;
  125. break;
  126. default:
  127. return VersionMismatch;
  128. }
  129. return Processing;
  130. }
  131. void Command::Restart() {
  132. state = State::CommandSent;
  133. logic->SendMsg(logic->command.rq);
  134. }
  135. StepStatus Command::Step() {
  136. switch (state) {
  137. case State::CommandSent: {
  138. auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
  139. if (expmsg != MessageReady)
  140. return expmsg;
  141. switch (logic->rsp.paramCode) { // the response should be either accepted or rejected
  142. case ResponseMsgParamCodes::Accepted:
  143. logic->progressCode = ProgressCode::OK;
  144. logic->errorCode = ErrorCode::RUNNING;
  145. state = State::Wait;
  146. break;
  147. case ResponseMsgParamCodes::Rejected:
  148. // rejected - should normally not happen, but report the error up
  149. logic->progressCode = ProgressCode::OK;
  150. logic->errorCode = ErrorCode::PROTOCOL_ERROR;
  151. return CommandRejected;
  152. default:
  153. return ProtocolError;
  154. }
  155. } break;
  156. case State::Wait:
  157. if (logic->Elapsed(heartBeatPeriod)) {
  158. SendQuery();
  159. } else {
  160. // even when waiting for a query period, we need to report a change in filament sensor's state
  161. // - it is vital for a precise synchronization of moves of the printer and the MMU
  162. CheckAndReportAsyncEvents();
  163. }
  164. break;
  165. case State::QuerySent: {
  166. auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
  167. if (expmsg != MessageReady)
  168. return expmsg;
  169. }
  170. // [[fallthrough]];
  171. case State::ContinueFromIdle:
  172. switch (logic->rsp.paramCode) {
  173. case ResponseMsgParamCodes::Processing:
  174. logic->progressCode = static_cast<ProgressCode>(logic->rsp.paramValue);
  175. logic->errorCode = ErrorCode::OK;
  176. SendAndUpdateFilamentSensor(); // keep on reporting the state of fsensor regularly
  177. break;
  178. case ResponseMsgParamCodes::Error:
  179. // in case of an error the progress code remains as it has been before
  180. logic->errorCode = static_cast<ErrorCode>(logic->rsp.paramValue);
  181. // keep on reporting the state of fsensor regularly even in command error state
  182. // - the MMU checks FINDA and fsensor even while recovering from errors
  183. SendAndUpdateFilamentSensor();
  184. return CommandError;
  185. case ResponseMsgParamCodes::Finished:
  186. logic->progressCode = ProgressCode::OK;
  187. state = State::Ready;
  188. return Finished;
  189. default:
  190. return ProtocolError;
  191. }
  192. break;
  193. case State::FilamentSensorStateSent:{
  194. auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
  195. if (expmsg != MessageReady)
  196. return expmsg;
  197. SendFINDAQuery();
  198. } break;
  199. case State::FINDAReqSent:
  200. return ProcessFINDAReqSent(Processing, State::Wait);
  201. case State::ButtonSent:{
  202. // button is never confirmed ... may be it should be
  203. // auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
  204. // if (expmsg != MessageReady)
  205. // return expmsg;
  206. SendQuery();
  207. } break;
  208. default:
  209. return ProtocolError;
  210. }
  211. return Processing;
  212. }
  213. void Idle::Restart() {
  214. state = State::Ready;
  215. }
  216. StepStatus Idle::Step() {
  217. switch (state) {
  218. case State::Ready: // check timeout
  219. if (logic->Elapsed(heartBeatPeriod)) {
  220. logic->SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
  221. state = State::QuerySent;
  222. return Processing;
  223. }
  224. break;
  225. case State::QuerySent: { // check UART
  226. auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
  227. if (expmsg != MessageReady)
  228. return expmsg;
  229. // 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.
  230. // That causes no issues here, we just need to switch to Command processing and continue there from now on.
  231. // 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.
  232. switch( logic->rsp.request.code ){
  233. case RequestMsgCodes::Cut:
  234. case RequestMsgCodes::Eject:
  235. case RequestMsgCodes::Load:
  236. case RequestMsgCodes::Mode:
  237. case RequestMsgCodes::Tool:
  238. case RequestMsgCodes::Unload:
  239. if( logic->rsp.paramCode != ResponseMsgParamCodes::Finished ){
  240. logic->SwitchFromIdleToCommand();
  241. return Processing;
  242. }
  243. default:
  244. break;
  245. }
  246. SendFINDAQuery();
  247. return Processing;
  248. } break;
  249. case State::FINDAReqSent:
  250. return ProcessFINDAReqSent(Finished, State::Ready);
  251. default:
  252. return ProtocolError;
  253. }
  254. // The "return Finished" in this state machine requires a bit of explanation:
  255. // The Idle state either did nothing (still waiting for the heartbeat timeout)
  256. // or just successfully received the answer to Q0, whatever that was.
  257. // In both cases, it is ready to hand over work to a command or something else,
  258. // therefore we are returning Finished (also to exit mmu_loop() and unblock Marlin's loop!).
  259. // If there is no work, we'll end up in the Idle state again
  260. // and we'll send the heartbeat message after the specified timeout.
  261. return Finished;
  262. }
  263. ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
  264. : stopped(this)
  265. , startSeq(this)
  266. , idle(this)
  267. , command(this)
  268. , currentState(&stopped)
  269. , plannedRq(RequestMsgCodes::unknown, 0)
  270. , lastUARTActivityMs(0)
  271. , rsp(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0)
  272. , state(State::Stopped)
  273. , lrb(0)
  274. , uart(uart)
  275. , lastFSensor((uint8_t)WhereIsFilament())
  276. {}
  277. void ProtocolLogic::Start() {
  278. state = State::InitSequence;
  279. currentState = &startSeq;
  280. startSeq.Restart();
  281. }
  282. void ProtocolLogic::Stop() {
  283. state = State::Stopped;
  284. currentState = &stopped;
  285. }
  286. void ProtocolLogic::ToolChange(uint8_t slot) {
  287. PlanGenericRequest(RequestMsg(RequestMsgCodes::Tool, slot));
  288. }
  289. void ProtocolLogic::UnloadFilament() {
  290. PlanGenericRequest(RequestMsg(RequestMsgCodes::Unload, 0));
  291. }
  292. void ProtocolLogic::LoadFilament(uint8_t slot) {
  293. PlanGenericRequest(RequestMsg(RequestMsgCodes::Load, slot));
  294. }
  295. void ProtocolLogic::EjectFilament(uint8_t slot) {
  296. PlanGenericRequest(RequestMsg(RequestMsgCodes::Eject, slot));
  297. }
  298. void ProtocolLogic::CutFilament(uint8_t slot){
  299. PlanGenericRequest(RequestMsg(RequestMsgCodes::Cut, slot));
  300. }
  301. void ProtocolLogic::ResetMMU() {
  302. PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, 0));
  303. }
  304. void ProtocolLogic::Button(uint8_t index){
  305. PlanGenericRequest(RequestMsg(RequestMsgCodes::Button, index));
  306. }
  307. void ProtocolLogic::Home(uint8_t mode){
  308. PlanGenericRequest(RequestMsg(RequestMsgCodes::Home, mode));
  309. }
  310. void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
  311. plannedRq = rq;
  312. if( ! currentState->ExpectsResponse() ){
  313. ActivatePlannedRequest();
  314. } // otherwise wait for an empty window to activate the request
  315. }
  316. bool MMU2::ProtocolLogic::ActivatePlannedRequest(){
  317. if( plannedRq.code == RequestMsgCodes::Button ){
  318. // only issue the button to the MMU and do not restart the state machines
  319. command.SendButton(plannedRq.value);
  320. plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
  321. return true;
  322. } else if( plannedRq.code != RequestMsgCodes::unknown ){
  323. currentState = &command;
  324. command.SetRequestMsg(plannedRq);
  325. plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
  326. command.Restart();
  327. return true;
  328. }
  329. return false;
  330. }
  331. void ProtocolLogic::SwitchFromIdleToCommand(){
  332. currentState = &command;
  333. command.SetRequestMsg(rsp.request);
  334. // we are recovering from a communication drop out, the command is already running
  335. // and we have just received a response to a Q0 message about a command progress
  336. command.ContinueFromIdle();
  337. }
  338. void ProtocolLogic::SwitchToIdle() {
  339. state = State::Running;
  340. currentState = &idle;
  341. idle.Restart();
  342. }
  343. void ProtocolLogic::HandleCommunicationTimeout() {
  344. uart->flush(); // clear the output buffer
  345. currentState = &startSeq;
  346. state = State::InitSequence;
  347. startSeq.Restart();
  348. }
  349. bool ProtocolLogic::Elapsed(uint32_t timeout) const {
  350. return _millis() >= (lastUARTActivityMs + timeout);
  351. }
  352. void ProtocolLogic::RecordUARTActivity() {
  353. lastUARTActivityMs = _millis();
  354. }
  355. void ProtocolLogic::RecordReceivedByte(uint8_t c){
  356. lastReceivedBytes[lrb] = c;
  357. lrb = (lrb+1) % lastReceivedBytes.size();
  358. }
  359. char NibbleToChar(uint8_t c){
  360. switch (c) {
  361. case 0:
  362. case 1:
  363. case 2:
  364. case 3:
  365. case 4:
  366. case 5:
  367. case 6:
  368. case 7:
  369. case 8:
  370. case 9:
  371. return c + '0';
  372. case 10:
  373. case 11:
  374. case 12:
  375. case 13:
  376. case 14:
  377. case 15:
  378. return (c - 10) + 'a';
  379. default:
  380. return 0;
  381. }
  382. }
  383. void ProtocolLogic::FormatLastReceivedBytes(char *dst){
  384. for(uint8_t i = 0; i < lastReceivedBytes.size(); ++i){
  385. uint8_t b = lastReceivedBytes[ (lrb-i-1) % lastReceivedBytes.size() ];
  386. dst[i*3] = NibbleToChar(b >> 4);
  387. dst[i*3+1] = NibbleToChar(b & 0xf);
  388. dst[i*3+2] = ' ';
  389. }
  390. dst[ (lastReceivedBytes.size() - 1) * 3 + 2] = 0; // terminate properly
  391. }
  392. void ProtocolLogic::FormatLastResponseMsgAndClearLRB(char *dst){
  393. *dst++ = '<';
  394. for(uint8_t i = 0; i < lrb; ++i){
  395. uint8_t b = lastReceivedBytes[ i ];
  396. if( b < 32 )b = '.';
  397. if( b > 127 )b = '.';
  398. *dst++ = b;
  399. }
  400. *dst = 0; // terminate properly
  401. lrb = 0; // reset the input buffer index in case of a clean message
  402. }
  403. void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size){
  404. constexpr uint_fast8_t rqs = modules::protocol::Protocol::MaxRequestSize() + 2;
  405. char tmp[rqs] = ">";
  406. static char lastMsg[rqs] = "";
  407. for(uint8_t i = 0; i < size; ++i){
  408. uint8_t b = txbuff[i];
  409. if( b < 32 )b = '.';
  410. if( b > 127 )b = '.';
  411. tmp[i+1] = b;
  412. }
  413. tmp[size+1] = '\n';
  414. tmp[size+2] = 0;
  415. if( !strncmp(tmp, ">S0.\n", rqs) && !strncmp(lastMsg, tmp, rqs) ){
  416. // @@TODO we skip the repeated request msgs for now
  417. // to avoid spoiling the whole log just with ">S0" messages
  418. // especially when the MMU is not connected.
  419. // We'll lose the ability to see if the printer is actually
  420. // trying to find the MMU, but since it has been reliable in the past
  421. // we can live without it for now.
  422. } else {
  423. MMU2_ECHO_MSG(tmp);
  424. }
  425. memcpy(lastMsg, tmp, rqs);
  426. }
  427. void MMU2::ProtocolLogic::LogError(const char *reason){
  428. char lrb[lastReceivedBytes.size() * 3];
  429. FormatLastReceivedBytes(lrb);
  430. MMU2_ERROR_MSG(reason);
  431. SERIAL_ECHO(", last bytes: ");
  432. SERIAL_ECHOLN(lrb);
  433. }
  434. void ProtocolLogic::LogResponse(){
  435. char lrb[lastReceivedBytes.size()];
  436. FormatLastResponseMsgAndClearLRB(lrb);
  437. MMU2_ECHO_MSG(lrb);
  438. SERIAL_ECHOLN();
  439. }
  440. StepStatus MMU2::ProtocolLogic::HandleCommError(const char *msg, StepStatus ss){
  441. protocol.ResetResponseDecoder();
  442. HandleCommunicationTimeout();
  443. if( dataTO.Record(ss) ){
  444. LogError(msg);
  445. return dataTO.InitialCause();
  446. } else {
  447. return Processing; // suppress short drop outs of communication
  448. }
  449. }
  450. StepStatus ProtocolLogic::Step() {
  451. if( ! currentState->ExpectsResponse() ){ // if not waiting for a response, activate a planned request immediately
  452. ActivatePlannedRequest();
  453. }
  454. auto currentStatus = currentState->Step();
  455. switch (currentStatus) {
  456. case Processing:
  457. // we are ok, the state machine continues correctly
  458. break;
  459. case Finished: {
  460. // We are ok, switching to Idle if there is no potential next request planned.
  461. // But the trouble is we must report a finished command if the previous command has just been finished
  462. // i.e. only try to find some planned command if we just finished the Idle cycle
  463. bool previousCommandFinished = currentState == &command; // @@TODO this is a nasty hack :(
  464. if( ! ActivatePlannedRequest() ){ // if nothing is planned, switch to Idle
  465. SwitchToIdle();
  466. } else {
  467. // if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
  468. if( ! previousCommandFinished && currentState == &command){
  469. currentStatus = Processing;
  470. }
  471. }
  472. }
  473. break;
  474. case CommandRejected:
  475. // we have to repeat it - that's the only thing we can do
  476. // no change in state
  477. // @@TODO wait until Q0 returns command in progress finished, then we can send this one
  478. LogError("Command rejected");
  479. command.Restart();
  480. break;
  481. case CommandError:
  482. LogError("Command Error");
  483. // we shall probably transfer into the Idle state and await further instructions from the upper layer
  484. // Idle state may solve the problem of keeping up the heart beat running
  485. break;
  486. case VersionMismatch:
  487. LogError("Version mismatch");
  488. Stop(); // cannot continue
  489. break;
  490. case ProtocolError:
  491. currentStatus = HandleCommError("Protocol error", ProtocolError);
  492. break;
  493. case CommunicationTimeout:
  494. currentStatus = HandleCommError("Communication timeout", CommunicationTimeout);
  495. break;
  496. default:
  497. break;
  498. }
  499. return currentStatus;
  500. }
  501. uint8_t ProtocolLogic::CommandInProgress() const {
  502. if( currentState != &command )
  503. return 0;
  504. return (uint8_t)command.ReqMsg().code;
  505. }
  506. bool DropOutFilter::Record(StepStatus ss){
  507. if( occurrences == maxOccurrences ){
  508. cause = ss;
  509. }
  510. --occurrences;
  511. return occurrences == 0;
  512. }
  513. } // namespace MMU2