瀏覽代碼

Refactoring of protocol logic to lower RAM consumption

Should also place the statistics request to the right spot in the state machine.
D.R.racer 2 年之前
父節點
當前提交
05ad1dc2f6
共有 2 個文件被更改,包括 321 次插入371 次删除
  1. 229 217
      Firmware/mmu2_protocol_logic.cpp
  2. 92 154
      Firmware/mmu2_protocol_logic.h

+ 229 - 217
Firmware/mmu2_protocol_logic.cpp

@@ -10,56 +10,46 @@ static constexpr uint8_t supportedMmuFWVersionMajor = 2;
 static constexpr uint8_t supportedMmuFWVersionMinor = 1;
 static constexpr uint8_t supportedMmuFWVersionBuild = 1;
 
-StepStatus ProtocolLogicPartBase::ProcessFINDAReqSent(StepStatus finishedRV, State nextState){
-    if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
-        return expmsg;
-    logic->findaPressed = logic->rsp.paramValue;
-    state = nextState;
-    return finishedRV;
-}
-
-StepStatus ProtocolLogicPartBase::ProcessStatisticsReqSent(StepStatus finishedRV, State nextState){
-    if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
-        return expmsg;
-    logic->fail_statistics = logic->rsp.paramValue;
-    state = nextState;
-    return finishedRV;
-}
-
-void ProtocolLogicPartBase::CheckAndReportAsyncEvents(){
+void ProtocolLogic::CheckAndReportAsyncEvents(){
     // even when waiting for a query period, we need to report a change in filament sensor's state
     // - it is vital for a precise synchronization of moves of the printer and the MMU
     uint8_t fs = (uint8_t)WhereIsFilament();
-    if( fs != logic->lastFSensor ){
+    if( fs != lastFSensor ){
         SendAndUpdateFilamentSensor();
     }
 }
 
-void ProtocolLogicPartBase::SendQuery(){
-    logic->SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
-    state = State::QuerySent;
+void ProtocolLogic::SendQuery(){
+    SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
+    scopeState = ScopeState::QuerySent;
+}
+
+void ProtocolLogic::SendFINDAQuery(){
+    SendMsg(RequestMsg(RequestMsgCodes::Finda, 0 ) );
+    scopeState = ScopeState::FINDAReqSent;
 }
 
-void ProtocolLogicPartBase::SendFINDAQuery(){
-    logic->SendMsg(RequestMsg(RequestMsgCodes::Finda, 0 ) );
-    state = State::FINDAReqSent;
+void ProtocolLogic::SendAndUpdateFilamentSensor(){
+    SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, lastFSensor = (uint8_t)WhereIsFilament() ) );
+    scopeState = ScopeState::FilamentSensorStateSent;
 }
 
-void ProtocolLogicPartBase::SendAndUpdateFilamentSensor(){
-    logic->SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, logic->lastFSensor = (uint8_t)WhereIsFilament() ) );
-    state = State::FilamentSensorStateSent;
+void ProtocolLogic::SendButton(uint8_t btn){
+    SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
+    scopeState = ScopeState::ButtonSent;
 }
 
-void ProtocolLogicPartBase::SendButton(uint8_t btn){
-    logic->SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
-    state = State::ButtonSent;
+void ProtocolLogic::SendVersion(uint8_t stage) {
+    SendMsg(RequestMsg(RequestMsgCodes::Version, stage));
+    scopeState = (ScopeState)((uint_fast8_t)ScopeState::S0Sent + stage);
 }
 
-void ProtocolLogicPartBase::SendVersion(uint8_t stage) {
-    logic->SendMsg(RequestMsg(RequestMsgCodes::Version, stage));
-    state = (State)((uint_fast8_t)State::S0Sent + stage);
+void ProtocolLogic::SendReadRegister(uint8_t index, ScopeState nextState) {
+    SendMsg(RequestMsg(RequestMsgCodes::Read, index));
+    scopeState = nextState;
 }
 
+
 // searches for "ok\n" in the incoming serial data (that's the usual response of the old MMU FW)
 struct OldMMUFWDetector {
     uint8_t ok;
@@ -113,7 +103,7 @@ StepStatus ProtocolLogic::ExpectingMessage(uint32_t timeout) {
                 break;
             }
             }
-            // otherwise [[fallthrough]]
+            [[fallthrough]]; // otherwise
         default:
             RecordUARTActivity(); // something has happened on the UART, update the timeout record
             return ProtocolError;
@@ -134,30 +124,39 @@ void ProtocolLogic::SendMsg(RequestMsg rq) {
     uart->write(txbuff, len);
     LogRequestMsg(txbuff, len);
     RecordUARTActivity();
-    if (rq.code == RequestMsgCodes::Version && rq.value == 3 ){
-        // Set the state so the value sent by MMU is read later
-        currentState->state = currentState->State::S3Sent;
-    }
 }
 
-void StartSeq::Restart() {
+void ProtocolLogic::StartSeqRestart() {
     retries = maxRetries;
     SendVersion(0);
 }
 
-StepStatus StartSeq::Step() {
-    if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
-        return expmsg;
+void ProtocolLogic::DelayedRestartRestart() {
+    scopeState = ScopeState::RecoveringProtocolError;
+}
+
+void ProtocolLogic::CommandRestart() {
+    scopeState = ScopeState::CommandSent;
+    SendMsg(rq);
+}
+
+void ProtocolLogic::IdleRestart() {
+    scopeState = ScopeState::Ready;
+}
 
+StepStatus ProtocolLogic::StartSeqStep(){
+    if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+        return expmsg;
+    
     // solve initial handshake
-    switch (state) {
-    case State::S0Sent: // received response to S0 - major
-        if( logic->rsp.request.code != RequestMsgCodes::Version || logic->rsp.request.value != 0 ){
+    switch (scopeState) {
+    case ScopeState::S0Sent: // received response to S0 - major
+        if( rsp.request.code != RequestMsgCodes::Version || 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 != supportedMmuFWVersionMajor) {
+            mmuFwVersionMajor = rsp.paramValue;
+            if (mmuFwVersionMajor != supportedMmuFWVersionMajor) {
                 if( --retries == 0){
                     // if (--retries == 0) has a specific meaning - since we are losing bytes on the UART for no obvious reason
                     // it can happen, that the reported version number is not complete - i.e. "1" instead of "19"
@@ -169,18 +168,18 @@ StepStatus StartSeq::Step() {
                     SendVersion(0);
                 }
             } else {
-                logic->dataTO.Reset(); // got meaningful response from the MMU, stop data layer timeout tracking
+                dataTO.Reset(); // got meaningful response from the MMU, stop data layer timeout tracking
                 SendVersion(1);
             }
         }
         break;
-    case State::S1Sent: // received response to S1 - minor
-        if( logic->rsp.request.code != RequestMsgCodes::Version || logic->rsp.request.value != 1 ){
+    case ScopeState::S1Sent: // received response to S1 - minor
+        if( rsp.request.code != RequestMsgCodes::Version || 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 != supportedMmuFWVersionMinor){
+            mmuFwVersionMinor = rsp.paramValue;
+            if (mmuFwVersionMinor != supportedMmuFWVersionMinor){
                 if( --retries == 0) {
                     return VersionMismatch;
                 } else {
@@ -191,13 +190,13 @@ StepStatus StartSeq::Step() {
             }
         }
         break;
-    case State::S2Sent: // received response to S2 - revision
-        if( logic->rsp.request.code != RequestMsgCodes::Version || logic->rsp.request.value != 2 ){
+    case ScopeState::S2Sent: // received response to S2 - revision
+        if( rsp.request.code != RequestMsgCodes::Version || 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 < supportedMmuFWVersionBuild){
+            mmuFwVersionBuild = rsp.paramValue;
+            if (mmuFwVersionBuild < supportedMmuFWVersionBuild){
                 if( --retries == 0 ) {
                     return VersionMismatch;
                 } else {
@@ -211,37 +210,33 @@ StepStatus StartSeq::Step() {
             }
         }
         break;
-    case State::FilamentSensorStateSent:
-        state = State::Ready;
-        logic->SwitchFromStartToIdle();
+    case ScopeState::FilamentSensorStateSent:
+        scopeState = ScopeState::Ready;
+        SwitchFromStartToIdle();
         return Processing; // Returning Finished is not a good idea in case of a fast error recovery
         // - it tells the printer, that the command which experienced a protocol error and recovered successfully actually terminated.
         // 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.
         break;
-    case State::RecoveringProtocolError:
+    case ScopeState::RecoveringProtocolError:
         // timer elapsed, clear the input buffer
-            while (logic->uart->read() >= 0)
-                ;
-            SendVersion(0);
+        while (uart->read() >= 0)
+            ;
+        SendVersion(0);
         break;
     default:
         return VersionMismatch;
     }
-    return Processing;
-}
-
-void DelayedRestart::Restart() {
-    state = State::RecoveringProtocolError;
+    return Finished;
 }
 
-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)
+StepStatus ProtocolLogic::DelayedRestartStep() {
+    switch (scopeState) {
+    case ScopeState::RecoveringProtocolError:
+        if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
+            while (uart->read() != -1)
                 ; // clear the input buffer
             // switch to StartSeq
-            logic->Start();
+            Start();
         }
         return Processing;
         break;
@@ -251,55 +246,50 @@ StepStatus DelayedRestart::Step() {
     return Finished;
 }
 
-void Command::Restart() {
-    state = State::CommandSent;
-    logic->SendMsg(logic->command.rq);
-}
-
-StepStatus Command::Step() {
-    switch (state) {
-    case State::CommandSent: {
-        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+StepStatus ProtocolLogic::CommandStep() {
+    switch (scopeState) {
+    case ScopeState::Wait:
+        if (Elapsed(heartBeatPeriod)) {
+            SendQuery();
+        } else { 
+            // even when waiting for a query period, we need to report a change in filament sensor's state
+            // - it is vital for a precise synchronization of moves of the printer and the MMU
+            CheckAndReportAsyncEvents();
+        }
+        break;
+    case ScopeState::CommandSent: {
+        if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
 
-        switch (logic->rsp.paramCode) { // the response should be either accepted or rejected
+        switch (rsp.paramCode) { // the response should be either accepted or rejected
         case ResponseMsgParamCodes::Accepted:
-            logic->progressCode = ProgressCode::OK;
-            logic->errorCode = ErrorCode::RUNNING;
-            state = State::Wait;
+            progressCode = ProgressCode::OK;
+            errorCode = ErrorCode::RUNNING;
+            scopeState = ScopeState::Wait;
             break;
         case ResponseMsgParamCodes::Rejected:
             // rejected - should normally not happen, but report the error up
-            logic->progressCode = ProgressCode::OK;
-            logic->errorCode = ErrorCode::PROTOCOL_ERROR;
+            progressCode = ProgressCode::OK;
+            errorCode = ErrorCode::PROTOCOL_ERROR;
             return CommandRejected;
         default:
             return ProtocolError;
         }
         } break;
-    case State::Wait:
-        if (logic->Elapsed(heartBeatPeriod)) {
-            SendQuery();
-        } else { 
-            // even when waiting for a query period, we need to report a change in filament sensor's state
-            // - it is vital for a precise synchronization of moves of the printer and the MMU
-            CheckAndReportAsyncEvents();
-        }
-        break;
-    case State::QuerySent:
-        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+    case ScopeState::QuerySent:
+        if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
         [[fallthrough]];
-    case State::ContinueFromIdle:
-        switch (logic->rsp.paramCode) {
+    case ScopeState::ContinueFromIdle:
+        switch (rsp.paramCode) {
         case ResponseMsgParamCodes::Processing:
-            logic->progressCode = static_cast<ProgressCode>(logic->rsp.paramValue);
-            logic->errorCode = ErrorCode::OK;
+            progressCode = static_cast<ProgressCode>(rsp.paramValue);
+            errorCode = ErrorCode::OK;
             SendAndUpdateFilamentSensor(); // keep on reporting the state of fsensor regularly
             break;
         case ResponseMsgParamCodes::Error:
             // in case of an error the progress code remains as it has been before
-            logic->errorCode = static_cast<ErrorCode>(logic->rsp.paramValue);
+            errorCode = static_cast<ErrorCode>(rsp.paramValue);
             // keep on reporting the state of fsensor regularly even in command error state
             // - the MMU checks FINDA and fsensor even while recovering from errors
             SendAndUpdateFilamentSensor();
@@ -307,118 +297,126 @@ StepStatus Command::Step() {
         case ResponseMsgParamCodes::Button:
             // The user pushed a button on the MMU. Save it, do what we need to do 
             // to prepare, then pass it back to the MMU so it can work its magic.
-            logic->buttonCode = static_cast<Buttons>(logic->rsp.paramValue);
+            buttonCode = static_cast<Buttons>(rsp.paramValue);
             SendAndUpdateFilamentSensor();
             return ButtonPushed;
         case ResponseMsgParamCodes::Finished:
-            logic->progressCode = ProgressCode::OK;
-            state = State::Ready;
+            progressCode = ProgressCode::OK;
+            scopeState = ScopeState::Ready;
             return Finished;
         default:
             return ProtocolError;
         }
         break;
-    case State::FilamentSensorStateSent:
-        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+    case ScopeState::FilamentSensorStateSent:
+        if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
         SendFINDAQuery();
-        break;
-    case State::S3Sent:
-        return ProcessStatisticsReqSent(Processing, State::Wait);
-    case State::FINDAReqSent:
-        return ProcessFINDAReqSent(Processing, State::Wait);
-    case State::ButtonSent:{
-        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+        scopeState = ScopeState::FINDAReqSent;
+        return Processing;
+    case ScopeState::FINDAReqSent:
+        if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+            return expmsg;
+        SendReadRegister(3, ScopeState::StatisticsSent);
+        scopeState = ScopeState::StatisticsSent;
+        return Processing;
+    case ScopeState::StatisticsSent:
+        if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+            return expmsg;
+        scopeState = ScopeState::Wait;
+        return Processing;
+    case ScopeState::ButtonSent:
+        if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             return expmsg;
-        if (logic->rsp.paramCode == ResponseMsgParamCodes::Accepted) {
+        if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
             // Button was accepted, decrement the retry.
             mmu2.DecrementRetryAttempts();
         }
         SendAndUpdateFilamentSensor();
-        } break;
+        break;
     default:
         return ProtocolError;
     }
     return Processing;
 }
 
-void Idle::Restart() {
-    state = State::Ready;
-}
-
-StepStatus Idle::Step() {
-    switch (state) {
-    case State::Ready: // check timeout
-        if (logic->Elapsed(heartBeatPeriod)) {
+StepStatus ProtocolLogic::IdleStep() {
+    if(scopeState == ScopeState::Ready){ // check timeout
+        if (Elapsed(heartBeatPeriod)) {
             SendQuery();
             return Processing;
         }
-        break;
-    case State::QuerySent: // check UART
-        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
+    } else {
+        if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
             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.
-        // That causes no issues here, we just need to switch to Command processing and continue there from now on.
-        // 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.
-        switch( logic->rsp.request.code ){
-        case RequestMsgCodes::Cut:
-        case RequestMsgCodes::Eject:
-        case RequestMsgCodes::Load:
-        case RequestMsgCodes::Mode:
-        case RequestMsgCodes::Tool:
-        case RequestMsgCodes::Unload:
-            if( logic->rsp.paramCode != ResponseMsgParamCodes::Finished ){
-                logic->SwitchFromIdleToCommand();
-                return Processing;
-            }
-            break;
-        case RequestMsgCodes::Reset:
-            // this one is kind of special
-            // we do not transfer to any "running" command (i.e. we stay in Idle),
-            // but in case there is an error reported we must make sure it gets propagated
-            switch( logic->rsp.paramCode ){
-            case ResponseMsgParamCodes::Button:
-                // The user pushed a button on the MMU. Save it, do what we need to do 
-                // to prepare, then pass it back to the MMU so it can work its magic.
-                logic->buttonCode = static_cast<Buttons>(logic->rsp.paramValue);
-                SendFINDAQuery();
-                return ButtonPushed;
-            case ResponseMsgParamCodes::Processing:
-                // @@TODO we may actually use this branch to report progress of manual operation on the MMU
-                // The MMU sends e.g. X0 P27 after its restart when the user presses an MMU button to move the Selector
-                // For now let's behave just like "finished"
-            case ResponseMsgParamCodes::Finished:
-                logic->errorCode = ErrorCode::OK;
+        
+        switch (scopeState) {
+        case ScopeState::QuerySent: // check UART
+            // 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.
+            // 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.
+            switch( rsp.request.code ){
+            case RequestMsgCodes::Cut:
+            case RequestMsgCodes::Eject:
+            case RequestMsgCodes::Load:
+            case RequestMsgCodes::Mode:
+            case RequestMsgCodes::Tool:
+            case RequestMsgCodes::Unload:
+                if( rsp.paramCode != ResponseMsgParamCodes::Finished ){
+                    SwitchFromIdleToCommand();
+                    return Processing;
+                }
+                break;
+            case RequestMsgCodes::Reset:
+                // this one is kind of special
+                // we do not transfer to any "running" command (i.e. we stay in Idle),
+                // but in case there is an error reported we must make sure it gets propagated
+                switch( rsp.paramCode ){
+                case ResponseMsgParamCodes::Button:
+                    // The user pushed a button on the MMU. Save it, do what we need to do 
+                    // to prepare, then pass it back to the MMU so it can work its magic.
+                    buttonCode = static_cast<Buttons>(rsp.paramValue);
+                    SendFINDAQuery();
+                    return ButtonPushed;
+                case ResponseMsgParamCodes::Processing:
+                    // @@TODO we may actually use this branch to report progress of manual operation on the MMU
+                    // The MMU sends e.g. X0 P27 after its restart when the user presses an MMU button to move the Selector
+                    // For now let's behave just like "finished"
+                case ResponseMsgParamCodes::Finished:
+                    errorCode = ErrorCode::OK;
+                    break;
+                default:
+                    errorCode = static_cast<ErrorCode>(rsp.paramValue);
+                    SendFINDAQuery(); // continue Idle state without restarting the communication
+                    return CommandError;
+                }
                 break;
             default:
-                logic->errorCode = static_cast<ErrorCode>(logic->rsp.paramValue);
-                SendFINDAQuery(); // continue Idle state without restarting the communication
-                return CommandError;
+                return ProtocolError;
             }
+            SendFINDAQuery();
+            return Processing;
+            break;
+        case ScopeState::FINDAReqSent:
+            SendReadRegister(3, ScopeState::StatisticsSent);
+            scopeState = ScopeState::StatisticsSent;
+            return Processing;
+        case ScopeState::StatisticsSent:
+            failStatistics = rsp.paramValue;
+            scopeState = ScopeState::Ready;
+            return Processing;
+        case ScopeState::ButtonSent:
+            if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
+                // Button was accepted, decrement the retry.
+                mmu2.DecrementRetryAttempts();
+            }
+            SendFINDAQuery();
             break;
         default:
             return ProtocolError;
         }
-        SendFINDAQuery();
-        return Processing;
-        break;
-    case State::S3Sent:
-        return ProcessStatisticsReqSent(Finished, State::Ready);
-    case State::FINDAReqSent:
-        return ProcessFINDAReqSent(Finished, State::Ready);
-    case State::ButtonSent:{
-        if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
-            return expmsg;
-        if (logic->rsp.paramCode == ResponseMsgParamCodes::Accepted) {
-            // Button was accepted, decrement the retry.
-            mmu2.DecrementRetryAttempts();
-        }
-        SendFINDAQuery();
-        } break;
-    default:
-        return ProtocolError;
     }
-
+    
     // The "return Finished" in this state machine requires a bit of explanation:
     // The Idle state either did nothing (still waiting for the heartbeat timeout)
     // or just successfully received the answer to Q0, whatever that was.
@@ -430,12 +428,8 @@ StepStatus Idle::Step() {
 }
 
 ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
-    : stopped(this)
-    , startSeq(this)
-    , delayedRestart(this)
-    , idle(this)
-    , command(this)
-    , currentState(&stopped)
+    : currentScope(Scope::Stopped)
+    , scopeState(ScopeState::Ready)
     , plannedRq(RequestMsgCodes::unknown, 0)
     , lastUARTActivityMs(0)
     , dataTO()
@@ -448,7 +442,7 @@ ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
     , buttonCode(NoButton)
     , lastFSensor((uint8_t)WhereIsFilament())
     , findaPressed(false)
-    , fail_statistics(0)
+    , failStatistics(0)
     , mmuFwVersionMajor(0)
     , mmuFwVersionMinor(0)
     , mmuFwVersionBuild(0)
@@ -456,14 +450,14 @@ ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
 
 void ProtocolLogic::Start() {
     state = State::InitSequence;
-    currentState = &startSeq;
+    currentScope = Scope::StartSeq;
     protocol.ResetResponseDecoder(); // important - finished delayed restart relies on this
-    startSeq.Restart();
+    StartSeqRestart();
 }
 
 void ProtocolLogic::Stop() {
     state = State::Stopped;
-    currentState = &stopped;
+    currentScope = Scope::Stopped;
 }
 
 void ProtocolLogic::ToolChange(uint8_t slot) {
@@ -504,7 +498,7 @@ void ProtocolLogic::Home(uint8_t mode){
 
 void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
     plannedRq = rq;
-    if( ! currentState->ExpectsResponse() ){
+    if( ! ExpectsResponse() ){
         ActivatePlannedRequest();
     } // otherwise wait for an empty window to activate the request
 }
@@ -512,39 +506,57 @@ void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
 bool ProtocolLogic::ActivatePlannedRequest(){
     if( plannedRq.code == RequestMsgCodes::Button ){
         // only issue the button to the MMU and do not restart the state machines
-        currentState->SendButton(plannedRq.value);
+        SendButton(plannedRq.value);
         plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
         return true;
     } else if( plannedRq.code != RequestMsgCodes::unknown ){
-        currentState = &command;
-        command.SetRequestMsg(plannedRq);
+        currentScope = Scope::Command;
+        SetRequestMsg(plannedRq);
         plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
-        command.Restart();
+        CommandRestart();
         return true;
     }
     return false;
 }
 
 void ProtocolLogic::SwitchFromIdleToCommand(){
-    currentState = &command;
-    command.SetRequestMsg(rsp.request);
+    currentScope = Scope::Command;
+    SetRequestMsg(rsp.request);
     // we are recovering from a communication drop out, the command is already running
     // and we have just received a response to a Q0 message about a command progress
-    command.ContinueFromIdle();
+    CommandContinueFromIdle();
 }
 
 void ProtocolLogic::SwitchToIdle() {
     state = State::Running;
-    currentState = &idle;
-    idle.Restart();
+    currentScope = Scope::Idle;
+    IdleRestart();
 }
 
 void ProtocolLogic::SwitchFromStartToIdle(){
     state = State::Running;
-    currentState = &idle;
-    idle.Restart();
-    idle.SendQuery(); // force sending Q0 immediately
-    idle.state = Idle::State::QuerySent;
+    currentScope = Scope::Idle;
+    IdleRestart();
+    SendQuery(); // force sending Q0 immediately
+    scopeState = ScopeState::QuerySent;
+}
+
+StepStatus ProtocolLogic::ScopeStep(){
+    switch(currentScope){
+    case Scope::StartSeq:
+        return StartSeqStep();
+    case Scope::DelayedRestart:
+        return DelayedRestartStep();
+    case Scope::Idle:
+        return IdleStep();
+    case Scope::Command:
+        return CommandStep();
+    case Scope::Stopped:
+        return StoppedStep();
+    default:
+        break;
+    }
+    return Finished;
 }
 
 bool ProtocolLogic::Elapsed(uint32_t timeout) const {
@@ -667,16 +679,16 @@ StepStatus ProtocolLogic::HandleCommunicationTimeout() {
 StepStatus ProtocolLogic::HandleProtocolError() {
     uart->flush(); // clear the output buffer
     state = State::InitSequence;
-    currentState = &delayedRestart;
-    delayedRestart.Restart();
+    currentScope = Scope::DelayedRestart;
+    DelayedRestartRestart();
     return SuppressShortDropOuts(PSTR("Protocol Error"), ProtocolError);
 }
 
 StepStatus ProtocolLogic::Step() {
-    if( ! currentState->ExpectsResponse() ){ // if not waiting for a response, activate a planned request immediately
+    if( ! ExpectsResponse() ){ // if not waiting for a response, activate a planned request immediately
         ActivatePlannedRequest();
     }
-    auto currentStatus = currentState->Step();
+    auto currentStatus = ScopeStep();
     switch (currentStatus) {
     case Processing:
         // we are ok, the state machine continues correctly
@@ -685,12 +697,12 @@ StepStatus ProtocolLogic::Step() {
         // We are ok, switching to Idle if there is no potential next request planned.
         // But the trouble is we must report a finished command if the previous command has just been finished
         // i.e. only try to find some planned command if we just finished the Idle cycle
-        bool previousCommandFinished = currentState == &command; // @@TODO this is a nasty hack :( 
+        bool previousCommandFinished = currentScope == Scope::Command; // @@TODO this is a nasty hack :( 
         if( ! ActivatePlannedRequest() ){ // if nothing is planned, switch to Idle
             SwitchToIdle();
         } else {
             // if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
-            if( ! previousCommandFinished && currentState == &command){
+            if( ! previousCommandFinished && currentScope == Scope::Command){
                 currentStatus = Processing;
             }
         }
@@ -700,7 +712,7 @@ StepStatus ProtocolLogic::Step() {
         // no change in state
         // @@TODO wait until Q0 returns command in progress finished, then we can send this one
         LogError(PSTR("Command rejected"));
-        command.Restart();
+        CommandRestart();
         break;
     case CommandError:
         LogError(PSTR("Command Error"));
@@ -724,9 +736,9 @@ StepStatus ProtocolLogic::Step() {
 }
 
 uint8_t ProtocolLogic::CommandInProgress() const {
-    if( currentState != &command )
+    if( currentScope != Scope::Command )
         return 0;
-    return (uint8_t)command.ReqMsg().code;
+    return (uint8_t)ReqMsg().code;
 }
 
 bool DropOutFilter::Record(StepStatus ss){

+ 92 - 154
Firmware/mmu2_protocol_logic.h

@@ -49,152 +49,6 @@ static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of
 
 static_assert( heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts");
 
-/// Base class for sub-automata of the ProtocolLogic class.
-/// Their operation should never block (wait inside).
-class ProtocolLogicPartBase {
-public:
-    inline ProtocolLogicPartBase(ProtocolLogic *logic)
-        : logic(logic)
-        , state(State::Ready) {}
-
-    /// Restarts the sub-automaton
-    virtual void Restart() = 0;
-
-    /// Makes one step in the sub-automaton
-    /// @returns StepStatus
-    virtual StepStatus Step() = 0;
-    
-    /// @returns true if the state machine is waiting for a response from the MMU
-    bool ExpectsResponse()const { return state != State::Ready && state != State::Wait; }
-
-protected:
-    ProtocolLogic *logic; ///< pointer to parent ProtocolLogic layer
-    friend class ProtocolLogic;
-    
-    /// Common internal states of the derived sub-automata
-    /// General rule of thumb: *Sent states are waiting for a response from the MMU
-    enum class State : uint_fast8_t { 
-        Ready,
-        Wait,
-
-        S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another
-        S1Sent,
-        S2Sent,
-        S3Sent,
-        QuerySent,
-        CommandSent,
-        FilamentSensorStateSent,
-        FINDAReqSent,
-        ButtonSent,
-
-        ContinueFromIdle,
-        RecoveringProtocolError
-    };
-
-    State state; ///< internal state of the sub-automaton
-
-    /// @returns the status of processing of the FINDA query response
-    /// @param finishedRV returned value in case the message was successfully received and processed
-    /// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
-    StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
-
-    /// @returns the status of processing of the statistics query response
-    /// @param finishedRV returned value in case the message was successfully received and processed
-    /// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
-    StepStatus ProcessStatisticsReqSent(StepStatus finishedRV, State nextState);
-
-    /// Called repeatedly while waiting for a query (Q0) period.
-    /// All event checks to report immediately from the printer to the MMU shall be done in this method.
-    /// So far, the only such a case is the filament sensor, but there can be more like this in the future.
-    void CheckAndReportAsyncEvents();
-    
-    void SendQuery();
-    
-    void SendFINDAQuery();
-    
-    void SendAndUpdateFilamentSensor();
-
-    void SendButton(uint8_t btn);
-
-    void SendVersion(uint8_t stage);
-};
-
-/// Starting sequence of the communication with the MMU.
-/// The printer shall ask for MMU's version numbers.
-/// If everything goes well and the MMU's version is good enough,
-/// the ProtocolLogic layer may continue talking to the MMU
-class StartSeq : public ProtocolLogicPartBase {
-public:
-    inline StartSeq(ProtocolLogic *logic)
-        : ProtocolLogicPartBase(logic)
-        , retries(maxRetries) {}
-    void Restart() override;
-    StepStatus Step() override;
-private:
-    static constexpr uint8_t maxRetries = 6;
-    uint8_t retries;
-};
-
-class DelayedRestart : public ProtocolLogicPartBase {
-public:
-    inline DelayedRestart(ProtocolLogic *logic)
-        : ProtocolLogicPartBase(logic) {}
-    void Restart() override;
-    StepStatus Step() override;
-};
-
-/// A command and its lifecycle.
-/// CommandSent:
-/// - the command was placed into the UART TX buffer, awaiting response from the MMU
-/// - if the MMU confirms the command, we'll wait for it to finish
-/// - if the MMU refuses the command, we report an error (should normally not happen unless someone is hacking the communication without waiting for the previous command to finish)
-/// Wait:
-/// - waiting for the MMU to process the command - may take several seconds, for example Tool change operation
-/// - meawhile, every 300ms we send a Q0 query to obtain the current state of the command being processed
-/// - as soon as we receive a response to Q0 from the MMU, we process it in the next state
-/// QuerySent - check the reply from the MMU - can be any of the following:
-/// - Processing: the MMU is still working
-/// - Error: the command failed on the MMU, we'll have the exact error report in the response message
-/// - Finished: the MMU finished the command successfully, another command may be issued now
-class Command : public ProtocolLogicPartBase {
-public:
-    inline Command(ProtocolLogic *logic)
-        : ProtocolLogicPartBase(logic)
-        , rq(RequestMsgCodes::unknown, 0) {}
-    void Restart() override;
-    StepStatus Step() override;
-    inline void SetRequestMsg(RequestMsg msg) {
-        rq = msg;
-    }
-    void ContinueFromIdle(){
-        state = State::ContinueFromIdle;
-    }
-    inline const RequestMsg &ReqMsg()const { return rq; }
-
-private:
-    RequestMsg rq;
-};
-
-/// Idle state - we have no command for the MMU, so we are only regularly querying its state with Q0 messages.
-/// The idle state can be interrupted any time to issue a command into the MMU
-class Idle : public ProtocolLogicPartBase {
-public:
-    inline Idle(ProtocolLogic *logic)
-        : ProtocolLogicPartBase(logic) {}
-    void Restart() override;
-    StepStatus Step() override;
-};
-
-/// The communication with the MMU is stopped/disabled (for whatever reason).
-/// Nothing is being put onto the UART.
-class Stopped : public ProtocolLogicPartBase {
-public:
-    inline Stopped(ProtocolLogic *logic)
-        : ProtocolLogicPartBase(logic) {}
-    void Restart() override {}
-    StepStatus Step() override { return Processing; }
-};
-
 ///< Filter of short consecutive drop outs which are recovered instantly
 class DropOutFilter {
     StepStatus cause;
@@ -259,7 +113,7 @@ public:
     }
 
     inline uint16_t FailStatistics() const {
-        return fail_statistics;
+        return failStatistics;
     }
 
     inline uint8_t MmuFwVersionMajor() const {
@@ -300,12 +154,96 @@ private:
     };
 
     // individual sub-state machines - may be they can be combined into a union since only one is active at once
-    Stopped stopped;
-    StartSeq startSeq;
-    DelayedRestart delayedRestart;
-    Idle idle;
-    Command command;
-    ProtocolLogicPartBase *currentState; ///< command currently being processed
+    // or we can blend them into ProtocolLogic at the cost of a less nice code (but hopefully shorter)
+//    Stopped stopped;
+//    StartSeq startSeq;
+//    DelayedRestart delayedRestart;
+//    Idle idle;
+//    Command command;
+//    ProtocolLogicPartBase *currentState; ///< command currently being processed
+    
+    enum class Scope : uint_fast8_t {
+        Stopped,
+        StartSeq,
+        DelayedRestart,
+        Idle,
+        Command
+    };
+    Scope currentScope;
+    
+    // basic scope members
+    /// @returns true if the state machine is waiting for a response from the MMU
+    bool ExpectsResponse()const { return scopeState != ScopeState::Ready && scopeState != ScopeState::Wait; }
+    
+    /// Common internal states of the derived sub-automata
+    /// General rule of thumb: *Sent states are waiting for a response from the MMU
+    enum class ScopeState : uint_fast8_t { 
+        Ready,
+        Wait,
+        
+        S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another
+        S1Sent,
+        S2Sent,
+        S3Sent,
+        QuerySent,
+        CommandSent,
+        FilamentSensorStateSent,
+        FINDAReqSent,
+        StatisticsSent,
+        ButtonSent,
+        
+        ContinueFromIdle,
+        RecoveringProtocolError
+    };
+    
+    ScopeState scopeState; ///< internal state of the sub-automaton
+    
+    /// @returns the status of processing of the FINDA query response
+    /// @param finishedRV returned value in case the message was successfully received and processed
+    /// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
+    // StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
+    
+    /// @returns the status of processing of the statistics query response
+    /// @param finishedRV returned value in case the message was successfully received and processed
+    /// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
+    // StepStatus ProcessStatisticsReqSent(StepStatus finishedRV, State nextState);
+    
+    /// Called repeatedly while waiting for a query (Q0) period.
+    /// All event checks to report immediately from the printer to the MMU shall be done in this method.
+    /// So far, the only such a case is the filament sensor, but there can be more like this in the future.
+    void CheckAndReportAsyncEvents();
+    void SendQuery();
+    void SendFINDAQuery();
+    void SendAndUpdateFilamentSensor();
+    void SendButton(uint8_t btn);
+    void SendVersion(uint8_t stage);
+    void SendReadRegister(uint8_t index, ScopeState nextState);
+    
+    /// Top level split - calls the appropriate step based on current scope
+    StepStatus ScopeStep();
+    
+    static constexpr uint8_t maxRetries = 6;
+    uint8_t retries;
+    
+    void StartSeqRestart();
+    void DelayedRestartRestart();
+    void IdleRestart();
+    void CommandRestart();
+    
+    StepStatus StartSeqStep();
+    StepStatus DelayedRestartStep();
+    StepStatus IdleStep();
+    StepStatus CommandStep();
+    StepStatus StoppedStep(){ return Processing; }
+    
+    inline void SetRequestMsg(RequestMsg msg) {
+        rq = msg;
+    }
+    void CommandContinueFromIdle(){
+        scopeState = ScopeState::ContinueFromIdle;
+    }
+    inline const RequestMsg &ReqMsg()const { return rq; }
+    RequestMsg rq = RequestMsg(RequestMsgCodes::unknown, 0);
     
     /// Records the next planned state, "unknown" msg code if no command is planned.
     /// This is not intended to be a queue of commands to process, protocol_logic must not queue commands.
@@ -345,7 +283,7 @@ private:
     uint8_t lastFSensor; ///< last state of filament sensor
 
     bool findaPressed;
-    uint16_t fail_statistics;
+    uint16_t failStatistics;
 
     uint8_t mmuFwVersionMajor, mmuFwVersionMinor;
     uint8_t mmuFwVersionBuild;