Selaa lähdekoodia

Fix protocol error recovery

Communication timeout and Protocol Errors are now distinguished.
In case of a Protocol Error, the printer waits for heartBeatTimeout to allow filling up the input UART buffer (we expect the MMU still produces some bytes).
Once the timeout elapsed, the input UART buffer is cleared and a new Start Sequence is initiated.
D.R.racer 2 vuotta sitten
vanhempi
commit
fa176c69db
2 muutettua tiedostoa jossa 123 lisäystä ja 73 poistoa
  1. 105 67
      Firmware/mmu2_protocol_logic.cpp
  2. 18 6
      Firmware/mmu2_protocol_logic.h

+ 105 - 67
Firmware/mmu2_protocol_logic.cpp

@@ -7,8 +7,7 @@
 namespace MMU2 {
 namespace MMU2 {
 
 
 StepStatus ProtocolLogicPartBase::ProcessFINDAReqSent(StepStatus finishedRV, State nextState){
 StepStatus ProtocolLogicPartBase::ProcessFINDAReqSent(StepStatus finishedRV, State nextState){
-    auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
-    if (expmsg != MessageReady)
+    if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
         return expmsg;
         return expmsg;
     logic->findaPressed = logic->rsp.paramValue;
     logic->findaPressed = logic->rsp.paramValue;
     state = nextState;
     state = nextState;
@@ -44,16 +43,9 @@ void ProtocolLogicPartBase::SendButton(uint8_t btn){
     state = State::ButtonSent;
     state = State::ButtonSent;
 }
 }
 
 
-StepStatus ProtocolLogic::ProcessUARTByte(uint8_t c) {
-    switch (protocol.DecodeResponse(c)) {
-    case DecodeStatus::MessageCompleted:
-        return MessageReady;
-    case DecodeStatus::NeedMoreData:
-        return Processing;
-    case DecodeStatus::Error:
-    default:
-        return ProtocolError;
-    }
+void ProtocolLogicPartBase::SendVersion(uint8_t stage) {
+    logic->SendMsg(RequestMsg(RequestMsgCodes::Version, stage));
+    state = (State)((uint_fast8_t)State::S0Sent + stage);
 }
 }
 
 
 // searches for "ok\n" in the incoming serial data (that's the usual response of the old MMU FW)
 // searches for "ok\n" in the incoming serial data (that's the usual response of the old MMU FW)
@@ -133,54 +125,92 @@ void ProtocolLogic::SendMsg(RequestMsg rq) {
 }
 }
 
 
 void StartSeq::Restart() {
 void StartSeq::Restart() {
-    state = State::S0Sent;
-    logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 0));
+    SendVersion(0);
 }
 }
 
 
 StepStatus StartSeq::Step() {
 StepStatus StartSeq::Step() {
-    auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
-    if (expmsg != MessageReady)
+    if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
         return expmsg;
         return expmsg;
 
 
     // solve initial handshake
     // solve initial handshake
     switch (state) {
     switch (state) {
     case State::S0Sent: // received response to S0 - major
     case State::S0Sent: // received response to S0 - major
-        logic->mmuFwVersionMajor = logic->rsp.paramValue;
-        if (logic->mmuFwVersionMajor != 2) {
-            return VersionMismatch;
+        if( logic->rsp.request.code != RequestMsgCodes::Version || logic->rsp.request.value != 0 ){
+            // got a response to something else - protocol corruption probably, repeat the query
+                SendVersion(0);
+        } else {
+            logic->mmuFwVersionMajor = logic->rsp.paramValue;
+            if (logic->mmuFwVersionMajor != 2) {
+                return VersionMismatch;
+            }
+            logic->dataTO.Reset(); // got meaningful response from the MMU, stop data layer timeout tracking
+                SendVersion(1);
         }
         }
-        logic->dataTO.Reset(); // got meaningful response from the MMU, stop data layer timeout tracking
-        logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 1));
-        state = State::S1Sent;
         break;
         break;
     case State::S1Sent: // received response to S1 - minor
     case State::S1Sent: // received response to S1 - minor
-        logic->mmuFwVersionMinor = logic->rsp.paramValue;
-        if (logic->mmuFwVersionMinor != 0) {
-            return VersionMismatch;
+        if( logic->rsp.request.code != RequestMsgCodes::Version || logic->rsp.request.value != 1 ){
+            // got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
+                SendVersion(1);
+        } else {
+            logic->mmuFwVersionMinor = logic->rsp.paramValue;
+            if (logic->mmuFwVersionMinor != 0) {
+                return VersionMismatch;
+            }
+                SendVersion(2);
         }
         }
-        logic->SendMsg(RequestMsg(RequestMsgCodes::Version, 2));
-        state = State::S2Sent;
         break;
         break;
     case State::S2Sent: // received response to S2 - revision
     case State::S2Sent: // received response to S2 - revision
-        logic->mmuFwVersionBuild = logic->rsp.paramValue;
-        if (logic->mmuFwVersionBuild < 18) {
-            return VersionMismatch;
+        if( logic->rsp.request.code != RequestMsgCodes::Version || logic->rsp.request.value != 2 ){
+            // got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
+                SendVersion(2);
+        } else {
+            logic->mmuFwVersionBuild = logic->rsp.paramValue;
+            if (logic->mmuFwVersionBuild < 18) {
+                return VersionMismatch;
+            }
+            // Start General Interrogation after line up.
+            // For now we just send the state of the filament sensor, but we may request
+            // data point states from the MMU as well. TBD in the future, especially with another protocol
+            SendAndUpdateFilamentSensor();
         }
         }
-        // Start General Interrogation after line up.
-        // For now we just send the state of the filament sensor, but we may request
-        // data point states from the MMU as well. TBD in the future, especially with another protocol
-        SendAndUpdateFilamentSensor();
         break;
         break;
     case State::FilamentSensorStateSent:
     case State::FilamentSensorStateSent:
         state = State::Ready;
         state = State::Ready;
         return Finished;
         return Finished;
         break;
         break;
+    case State::RecoveringProtocolError:
+        // timer elapsed, clear the input buffer
+            while (logic->uart->read() >= 0)
+                ;
+            SendVersion(0);
+        break;
     default:
     default:
         return VersionMismatch;
         return VersionMismatch;
     }
     }
     return Processing;
     return Processing;
 }
 }
 
 
+void DelayedRestart::Restart() {
+    state = State::RecoveringProtocolError;
+}
+
+StepStatus DelayedRestart::Step() {
+    switch (state) {
+    case State::RecoveringProtocolError:
+        if (logic->Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
+            while (logic->uart->read() != -1)
+                ; // clear the input buffer
+            // switch to StartSeq
+            logic->Start();
+        }
+        return Processing;
+        break;
+    default:
+        break;
+    }
+    return Finished;
+}
+
 void Command::Restart() {
 void Command::Restart() {
     state = State::CommandSent;
     state = State::CommandSent;
     logic->SendMsg(logic->command.rq);
     logic->SendMsg(logic->command.rq);
@@ -189,8 +219,7 @@ void Command::Restart() {
 StepStatus Command::Step() {
 StepStatus Command::Step() {
     switch (state) {
     switch (state) {
     case State::CommandSent: {
     case State::CommandSent: {
-        auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
-        if (expmsg != MessageReady)
+        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
             return expmsg;
 
 
         switch (logic->rsp.paramCode) { // the response should be either accepted or rejected
         switch (logic->rsp.paramCode) { // the response should be either accepted or rejected
@@ -217,12 +246,10 @@ StepStatus Command::Step() {
             CheckAndReportAsyncEvents();
             CheckAndReportAsyncEvents();
         }
         }
         break;
         break;
-    case State::QuerySent: {
-        auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
-        if (expmsg != MessageReady)
+    case State::QuerySent:
+        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
             return expmsg;
-        }
-        // [[fallthrough]];
+        [[fallthrough]];
     case State::ContinueFromIdle:
     case State::ContinueFromIdle:
         switch (logic->rsp.paramCode) {
         switch (logic->rsp.paramCode) {
         case ResponseMsgParamCodes::Processing:
         case ResponseMsgParamCodes::Processing:
@@ -251,21 +278,18 @@ StepStatus Command::Step() {
             return ProtocolError;
             return ProtocolError;
         }
         }
         break;
         break;
-    case State::FilamentSensorStateSent:{
-        auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
-        if (expmsg != MessageReady)
+    case State::FilamentSensorStateSent:
+        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
             return expmsg;
         SendFINDAQuery();
         SendFINDAQuery();
-        } break;
+        break;
     case State::FINDAReqSent:
     case State::FINDAReqSent:
         return ProcessFINDAReqSent(Processing, State::Wait);
         return ProcessFINDAReqSent(Processing, State::Wait);
     case State::ButtonSent:{
     case State::ButtonSent:{
         // button is never confirmed ... may be it should be
         // button is never confirmed ... may be it should be
-        auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
-        if (expmsg != MessageReady)
+        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
             return expmsg;
-        if (logic->rsp.paramCode == ResponseMsgParamCodes::Accepted)
-        {
+        if (logic->rsp.paramCode == ResponseMsgParamCodes::Accepted) {
             // Button was accepted, decrement the retry.
             // Button was accepted, decrement the retry.
             mmu2.DecrementRetryAttempts();
             mmu2.DecrementRetryAttempts();
         }
         }
@@ -289,9 +313,8 @@ StepStatus Idle::Step() {
             return Processing;
             return Processing;
         }
         }
         break;
         break;
-    case State::QuerySent: { // check UART
-        auto expmsg = logic->ExpectingMessage(linkLayerTimeout);
-        if (expmsg != MessageReady)
+    case State::QuerySent: // check UART
+        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
             return expmsg;
         // 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.
         // 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.
         // That causes no issues here, we just need to switch to Command processing and continue there from now on.
         // That causes no issues here, we just need to switch to Command processing and continue there from now on.
@@ -331,7 +354,7 @@ StepStatus Idle::Step() {
         }
         }
         SendFINDAQuery();
         SendFINDAQuery();
         return Processing;
         return Processing;
-    } break;
+        break;
     case State::FINDAReqSent:
     case State::FINDAReqSent:
         return ProcessFINDAReqSent(Finished, State::Ready);
         return ProcessFINDAReqSent(Finished, State::Ready);
     default:
     default:
@@ -351,21 +374,31 @@ StepStatus Idle::Step() {
 ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
 ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
     : stopped(this)
     : stopped(this)
     , startSeq(this)
     , startSeq(this)
+    , delayedRestart(this)
     , idle(this)
     , idle(this)
     , command(this)
     , command(this)
     , currentState(&stopped)
     , currentState(&stopped)
     , plannedRq(RequestMsgCodes::unknown, 0)
     , plannedRq(RequestMsgCodes::unknown, 0)
     , lastUARTActivityMs(0)
     , lastUARTActivityMs(0)
+    , dataTO()
     , rsp(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0)
     , rsp(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0)
     , state(State::Stopped)
     , state(State::Stopped)
     , lrb(0)
     , lrb(0)
     , uart(uart)
     , uart(uart)
+    , errorCode(ErrorCode::OK)
+    , progressCode(ProgressCode::OK)
+    , buttonCode(NoButton)
     , lastFSensor((uint8_t)WhereIsFilament())
     , lastFSensor((uint8_t)WhereIsFilament())
+    , findaPressed(false)
+    , mmuFwVersionMajor(0)
+    , mmuFwVersionMinor(0)
+    , mmuFwVersionBuild(0)
 {}
 {}
 
 
 void ProtocolLogic::Start() {
 void ProtocolLogic::Start() {
     state = State::InitSequence;
     state = State::InitSequence;
     currentState = &startSeq;
     currentState = &startSeq;
+    protocol.ResetResponseDecoder(); // important - finished delayed restart relies on this
     startSeq.Restart();
     startSeq.Restart();
 }
 }
 
 
@@ -446,13 +479,6 @@ void ProtocolLogic::SwitchToIdle() {
     idle.Restart();
     idle.Restart();
 }
 }
 
 
-void ProtocolLogic::HandleCommunicationTimeout() {
-    uart->flush(); // clear the output buffer
-    currentState = &startSeq;
-    state = State::InitSequence;
-    startSeq.Restart();
-}
-
 bool ProtocolLogic::Elapsed(uint32_t timeout) const {
 bool ProtocolLogic::Elapsed(uint32_t timeout) const {
     return _millis() >= (lastUARTActivityMs + timeout);
     return _millis() >= (lastUARTActivityMs + timeout);
 }
 }
@@ -554,9 +580,7 @@ void ProtocolLogic::LogResponse(){
     SERIAL_ECHOLN();
     SERIAL_ECHOLN();
 }
 }
 
 
-StepStatus ProtocolLogic::HandleCommError(const char *msg, StepStatus ss){
-    protocol.ResetResponseDecoder();
-    HandleCommunicationTimeout();
+StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg, StepStatus ss) {
     if( dataTO.Record(ss) ){
     if( dataTO.Record(ss) ){
         LogError(msg);
         LogError(msg);
         return dataTO.InitialCause();
         return dataTO.InitialCause();
@@ -565,6 +589,21 @@ StepStatus ProtocolLogic::HandleCommError(const char *msg, StepStatus ss){
     }
     }
 }
 }
 
 
+StepStatus ProtocolLogic::HandleCommunicationTimeout() {
+    uart->flush(); // clear the output buffer
+    protocol.ResetResponseDecoder();
+    Start();
+    return SuppressShortDropOuts("Communication timeout", CommunicationTimeout);
+}
+
+StepStatus ProtocolLogic::HandleProtocolError() {
+    uart->flush(); // clear the output buffer
+    state = State::InitSequence;
+    currentState = &delayedRestart;
+    delayedRestart.Restart();
+    return SuppressShortDropOuts("Protocol Error", ProtocolError);
+}
+
 StepStatus ProtocolLogic::Step() {
 StepStatus ProtocolLogic::Step() {
     if( ! currentState->ExpectsResponse() ){ // if not waiting for a response, activate a planned request immediately
     if( ! currentState->ExpectsResponse() ){ // if not waiting for a response, activate a planned request immediately
         ActivatePlannedRequest();
         ActivatePlannedRequest();
@@ -587,8 +626,7 @@ StepStatus ProtocolLogic::Step() {
                 currentStatus = Processing;
                 currentStatus = Processing;
             }
             }
         }
         }
-        }
-        break;
+    } break;
     case CommandRejected:
     case CommandRejected:
         // we have to repeat it - that's the only thing we can do
         // we have to repeat it - that's the only thing we can do
         // no change in state
         // no change in state
@@ -606,10 +644,10 @@ StepStatus ProtocolLogic::Step() {
         Stop(); // cannot continue
         Stop(); // cannot continue
         break;
         break;
     case ProtocolError:
     case ProtocolError:
-        currentStatus = HandleCommError("Protocol error", ProtocolError);
+        currentStatus = HandleProtocolError();
         break;
         break;
     case CommunicationTimeout:
     case CommunicationTimeout:
-        currentStatus = HandleCommError("Communication timeout", CommunicationTimeout);
+        currentStatus = HandleCommunicationTimeout();
         break;
         break;
     default:
     default:
         break;
         break;

+ 18 - 6
Firmware/mmu2_protocol_logic.h

@@ -77,7 +77,7 @@ protected:
         Ready,
         Ready,
         Wait,
         Wait,
 
 
-        S0Sent,
+        S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another
         S1Sent,
         S1Sent,
         S2Sent,
         S2Sent,
         QuerySent,
         QuerySent,
@@ -86,7 +86,8 @@ protected:
         FINDAReqSent,
         FINDAReqSent,
         ButtonSent,
         ButtonSent,
 
 
-        ContinueFromIdle
+        ContinueFromIdle,
+        RecoveringProtocolError
     };
     };
 
 
     State state; ///< internal state of the sub-automaton
     State state; ///< internal state of the sub-automaton
@@ -108,6 +109,8 @@ protected:
     void SendAndUpdateFilamentSensor();
     void SendAndUpdateFilamentSensor();
 
 
     void SendButton(uint8_t btn);
     void SendButton(uint8_t btn);
+
+    void SendVersion(uint8_t stage);
 };
 };
 
 
 /// Starting sequence of the communication with the MMU.
 /// Starting sequence of the communication with the MMU.
@@ -122,6 +125,14 @@ public:
     StepStatus Step() override;
     StepStatus Step() override;
 };
 };
 
 
+class DelayedRestart : public ProtocolLogicPartBase {
+public:
+    inline DelayedRestart(ProtocolLogic *logic)
+        : ProtocolLogicPartBase(logic) {}
+    void Restart() override;
+    StepStatus Step() override;
+};
+
 /// A command and its lifecycle.
 /// A command and its lifecycle.
 /// CommandSent:
 /// CommandSent:
 /// - the command was placed into the UART TX buffer, awaiting response from the MMU
 /// - the command was placed into the UART TX buffer, awaiting response from the MMU
@@ -250,13 +261,12 @@ public:
 #ifndef UNITTEST
 #ifndef UNITTEST
 private:
 private:
 #endif
 #endif
-    
-    StepStatus ProcessUARTByte(uint8_t c);
     StepStatus ExpectingMessage(uint32_t timeout);
     StepStatus ExpectingMessage(uint32_t timeout);
     void SendMsg(RequestMsg rq);
     void SendMsg(RequestMsg rq);
     void SwitchToIdle();
     void SwitchToIdle();
-    void HandleCommunicationTimeout();
-    StepStatus HandleCommError(const char *msg, StepStatus ss);
+    StepStatus SuppressShortDropOuts(const char *msg, StepStatus ss);
+    StepStatus HandleCommunicationTimeout();
+    StepStatus HandleProtocolError();
     bool Elapsed(uint32_t timeout) const;
     bool Elapsed(uint32_t timeout) const;
     void RecordUARTActivity();
     void RecordUARTActivity();
     void RecordReceivedByte(uint8_t c);
     void RecordReceivedByte(uint8_t c);
@@ -276,6 +286,7 @@ private:
     // individual sub-state machines - may be they can be combined into a union since only one is active at once
     // individual sub-state machines - may be they can be combined into a union since only one is active at once
     Stopped stopped;
     Stopped stopped;
     StartSeq startSeq;
     StartSeq startSeq;
+    DelayedRestart delayedRestart;
     Idle idle;
     Idle idle;
     Command command;
     Command command;
     ProtocolLogicPartBase *currentState; ///< command currently being processed
     ProtocolLogicPartBase *currentState; ///< command currently being processed
@@ -327,6 +338,7 @@ private:
     friend class Command;
     friend class Command;
     friend class Idle;
     friend class Idle;
     friend class StartSeq;
     friend class StartSeq;
+    friend class DelayedRestart;
 
 
     friend class MMU2;
     friend class MMU2;
 };
 };