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 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 ProtocolLogicPartBase::SendFINDAQuery(){
logic->SendMsg(RequestMsg(RequestMsgCodes::Finda, 0 ) );
state = State::FINDAReqSent;
void ProtocolLogic::SendFINDAQuery(){
SendMsg(RequestMsg(RequestMsgCodes::Finda, 0 ) );
scopeState = ScopeState::FINDAReqSent;
}
void ProtocolLogicPartBase::SendAndUpdateFilamentSensor(){
logic->SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, logic->lastFSensor = (uint8_t)WhereIsFilament() ) );
state = State::FilamentSensorStateSent;
void ProtocolLogic::SendAndUpdateFilamentSensor(){
SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, lastFSensor = (uint8_t)WhereIsFilament() ) );
scopeState = ScopeState::FilamentSensorStateSent;
}
void ProtocolLogicPartBase::SendButton(uint8_t btn){
logic->SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
state = State::ButtonSent;
void ProtocolLogic::SendButton(uint8_t btn){
SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
scopeState = ScopeState::ButtonSent;
}
void ProtocolLogicPartBase::SendVersion(uint8_t stage) {
logic->SendMsg(RequestMsg(RequestMsgCodes::Version, stage));
state = (State)((uint_fast8_t)State::S0Sent + stage);
void ProtocolLogic::SendVersion(uint8_t stage) {
SendMsg(RequestMsg(RequestMsgCodes::Version, 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)
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;
return Finished;
}
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)
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,34 +246,10 @@ 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)
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)) {
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
@ -286,20 +257,39 @@ StepStatus Command::Step() {
CheckAndReportAsyncEvents();
}
break;
case State::QuerySent:
if (auto expmsg = logic->ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
case ScopeState::CommandSent: {
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;
[[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;
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.
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){

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");
/// 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;