Refactoring of protocol logic to lower RAM consumption

Should also place the statistics request to the right spot in the state machine.
This commit is contained in:
D.R.racer 2022-08-24 18:59:31 +02:00
parent dfddf3eaa5
commit 05ad1dc2f6
2 changed files with 325 additions and 375 deletions

View File

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

View File

@ -49,152 +49,6 @@ static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of
static_assert( heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts"); 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 ///< Filter of short consecutive drop outs which are recovered instantly
class DropOutFilter { class DropOutFilter {
StepStatus cause; StepStatus cause;
@ -259,7 +113,7 @@ public:
} }
inline uint16_t FailStatistics() const { inline uint16_t FailStatistics() const {
return fail_statistics; return failStatistics;
} }
inline uint8_t MmuFwVersionMajor() const { 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 // individual sub-state machines - may be they can be combined into a union since only one is active at once
Stopped stopped; // or we can blend them into ProtocolLogic at the cost of a less nice code (but hopefully shorter)
StartSeq startSeq; // Stopped stopped;
DelayedRestart delayedRestart; // StartSeq startSeq;
Idle idle; // DelayedRestart delayedRestart;
Command command; // Idle idle;
ProtocolLogicPartBase *currentState; ///< command currently being processed // 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. /// 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. /// 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 uint8_t lastFSensor; ///< last state of filament sensor
bool findaPressed; bool findaPressed;
uint16_t fail_statistics; uint16_t failStatistics;
uint8_t mmuFwVersionMajor, mmuFwVersionMinor; uint8_t mmuFwVersionMajor, mmuFwVersionMinor;
uint8_t mmuFwVersionBuild; uint8_t mmuFwVersionBuild;