Optimize MMU protocol logic

This commit is contained in:
D.R.racer 2022-08-29 17:50:39 +02:00
parent 05ad1dc2f6
commit 6c0d3b0b78
3 changed files with 307 additions and 338 deletions

View File

@ -173,7 +173,7 @@ public:
/// In the future we'll return the trully detected FW version /// In the future we'll return the trully detected FW version
Version GetMMUFWVersion()const { Version GetMMUFWVersion()const {
if( State() == xState::Active ){ if( State() == xState::Active ){
return { logic.MmuFwVersionMajor(), logic.MmuFwVersionMinor(), logic.MmuFwVersionBuild() }; return { logic.MmuFwVersionMajor(), logic.MmuFwVersionMinor(), logic.MmuFwVersionRevision() };
} else { } else {
return { 0, 0, 0}; return { 0, 0, 0};
} }

View File

@ -6,35 +6,33 @@
namespace MMU2 { namespace MMU2 {
static constexpr uint8_t supportedMmuFWVersionMajor = 2; static const uint8_t supportedMmuFWVersion[3] PROGMEM = { 2, 1, 1 };
static constexpr uint8_t supportedMmuFWVersionMinor = 1;
static constexpr uint8_t supportedMmuFWVersionBuild = 1;
void ProtocolLogic::CheckAndReportAsyncEvents(){ void ProtocolLogic::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 != lastFSensor ){ if (fs != lastFSensor) {
SendAndUpdateFilamentSensor(); SendAndUpdateFilamentSensor();
} }
} }
void ProtocolLogic::SendQuery(){ void ProtocolLogic::SendQuery() {
SendMsg(RequestMsg(RequestMsgCodes::Query, 0)); SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
scopeState = ScopeState::QuerySent; scopeState = ScopeState::QuerySent;
} }
void ProtocolLogic::SendFINDAQuery(){ void ProtocolLogic::SendFINDAQuery() {
SendMsg(RequestMsg(RequestMsgCodes::Finda, 0 ) ); SendMsg(RequestMsg(RequestMsgCodes::Finda, 0));
scopeState = ScopeState::FINDAReqSent; scopeState = ScopeState::FINDAReqSent;
} }
void ProtocolLogic::SendAndUpdateFilamentSensor(){ void ProtocolLogic::SendAndUpdateFilamentSensor() {
SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, lastFSensor = (uint8_t)WhereIsFilament() ) ); SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, lastFSensor = (uint8_t)WhereIsFilament()));
scopeState = ScopeState::FilamentSensorStateSent; scopeState = ScopeState::FilamentSensorStateSent;
} }
void ProtocolLogic::SendButton(uint8_t btn){ void ProtocolLogic::SendButton(uint8_t btn) {
SendMsg(RequestMsg(RequestMsgCodes::Button, btn)); SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
scopeState = ScopeState::ButtonSent; scopeState = ScopeState::ButtonSent;
} }
@ -73,14 +71,14 @@ struct OldMMUFWDetector {
} }
}; };
StepStatus ProtocolLogic::ExpectingMessage(uint32_t timeout) { StepStatus ProtocolLogic::ExpectingMessage() {
int bytesConsumed = 0; int bytesConsumed = 0;
int c = -1; int c = -1;
OldMMUFWDetector oldMMUh4x0r; // old MMU FW hacker ;) OldMMUFWDetector oldMMUh4x0r; // old MMU FW hacker ;)
// try to consume as many rx bytes as possible (until a message has been completed) // try to consume as many rx bytes as possible (until a message has been completed)
while((c = uart->read()) >= 0){ while ((c = uart->read()) >= 0) {
++bytesConsumed; ++bytesConsumed;
RecordReceivedByte(c); RecordReceivedByte(c);
switch (protocol.DecodeResponse(c)) { switch (protocol.DecodeResponse(c)) {
@ -109,10 +107,10 @@ StepStatus ProtocolLogic::ExpectingMessage(uint32_t timeout) {
return ProtocolError; return ProtocolError;
} }
} }
if( bytesConsumed != 0 ){ if (bytesConsumed != 0) {
RecordUARTActivity(); // something has happened on the UART, update the timeout record RecordUARTActivity(); // something has happened on the UART, update the timeout record
return Processing; // consumed some bytes, but message still not ready return Processing; // consumed some bytes, but message still not ready
} else if (Elapsed(timeout)) { } else if (Elapsed(linkLayerTimeout)) {
return CommunicationTimeout; return CommunicationTimeout;
} }
return Processing; return Processing;
@ -144,123 +142,147 @@ void ProtocolLogic::IdleRestart() {
scopeState = ScopeState::Ready; scopeState = ScopeState::Ready;
} }
StepStatus ProtocolLogic::StartSeqStep(){ StepStatus ProtocolLogic::ProcessVersionResponse(uint8_t stage) {
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady) if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != stage) {
return expmsg; // got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
SendVersion(stage);
} else {
mmuFwVersion[stage] = rsp.paramValue;
if (mmuFwVersion[stage] != pgm_read_byte(supportedMmuFWVersion[stage])) {
if (--retries == 0) {
return VersionMismatch;
} else {
SendVersion(stage);
}
} else {
dataTO.Reset(); // got a meaningful response from the MMU, stop data layer timeout tracking
SendVersion(stage + 1);
}
}
return Processing;
}
StepStatus ProtocolLogic::ScopeStep() {
if ((uint_fast8_t)scopeState & (uint8_t)ScopeState::NotExpectsResponse) {
// we are waiting for something
switch (currentScope) {
case Scope::DelayedRestart:
return DelayedRestartWait();
case Scope::Idle:
return IdleWait();
case Scope::Command:
return CommandWait();
case Scope::Stopped:
return StoppedStep();
default:
break;
}
} else {
// we are expecting a message
if (auto expmsg = ExpectingMessage(); expmsg != MessageReady) // this whole statement takes 12B
return expmsg;
// process message
switch (currentScope) {
case Scope::StartSeq:
return StartSeqStep(); // ~270B
case Scope::Idle:
return IdleStep(); // ~300B
case Scope::Command:
return CommandStep(); // ~430B
case Scope::Stopped:
return StoppedStep();
default:
break;
}
}
return Finished;
}
StepStatus ProtocolLogic::StartSeqStep() {
// solve initial handshake // solve initial handshake
switch (scopeState) { switch (scopeState) {
case ScopeState::S0Sent: // received response to S0 - major 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 {
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"
// Therefore we drop the MMU only if we run out of retries for this very reason.
// There is a limited amount of retries per the whole start seq.
// We also must be able to actually detect an unsupported MMU FW version, so the amount of retries shall be kept small.
return VersionMismatch;
} else {
SendVersion(0);
}
} else {
dataTO.Reset(); // got meaningful response from the MMU, stop data layer timeout tracking
SendVersion(1);
}
}
break;
case ScopeState::S1Sent: // received response to S1 - minor case ScopeState::S1Sent: // received response to S1 - minor
if( rsp.request.code != RequestMsgCodes::Version || rsp.request.value != 1 ){ case ScopeState::S2Sent: // received response to S2 - minor
return ProcessVersionResponse((uint8_t)scopeState - (uint8_t)ScopeState::S0Sent);
case ScopeState::S3Sent: // received response to S3 - revision
if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != 3) {
// 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(3);
} else { } else {
mmuFwVersionMinor = rsp.paramValue; mmuFwVersionBuild = rsp.paramValue; // just register the build number
if (mmuFwVersionMinor != supportedMmuFWVersionMinor){ // Start General Interrogation after line up.
if( --retries == 0) { // For now we just send the state of the filament sensor, but we may request
return VersionMismatch; // data point states from the MMU as well. TBD in the future, especially with another protocol
} else { SendAndUpdateFilamentSensor();
SendVersion(1);
}
} else {
SendVersion(2);
}
} }
break; return Processing;
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 {
mmuFwVersionBuild = rsp.paramValue;
if (mmuFwVersionBuild < supportedMmuFWVersionBuild){
if( --retries == 0 ) {
return VersionMismatch;
} else {
SendVersion(2);
}
} else {
// Start General Interrogation after line up.
// For now we just send the state of the filament sensor, but we may request
// data point states from the MMU as well. TBD in the future, especially with another protocol
SendAndUpdateFilamentSensor();
}
}
break;
case ScopeState::FilamentSensorStateSent: case ScopeState::FilamentSensorStateSent:
scopeState = ScopeState::Ready;
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 ScopeState::RecoveringProtocolError:
// timer elapsed, clear the input buffer
while (uart->read() >= 0)
;
SendVersion(0);
break;
default: default:
return VersionMismatch; return VersionMismatch;
} }
return Finished; return Finished;
} }
StepStatus ProtocolLogic::DelayedRestartStep() { StepStatus ProtocolLogic::DelayedRestartWait() {
switch (scopeState) { if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
case ScopeState::RecoveringProtocolError: while (uart->read() != -1)
if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on ; // clear the input buffer
while (uart->read() != -1) // switch to StartSeq
; // clear the input buffer Start();
// switch to StartSeq }
Start(); return Processing;
} }
return Processing;
break; StepStatus ProtocolLogic::CommandWait() {
default: if (Elapsed(heartBeatPeriod)) {
break; 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();
}
return Processing;
}
StepStatus ProtocolLogic::ProcessCommandQueryResponse() {
switch (rsp.paramCode) {
case ResponseMsgParamCodes::Processing:
progressCode = static_cast<ProgressCode>(rsp.paramValue);
errorCode = ErrorCode::OK;
SendAndUpdateFilamentSensor(); // keep on reporting the state of fsensor regularly
return Processing;
case ResponseMsgParamCodes::Error:
// in case of an error the progress code remains as it has been before
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();
return CommandError;
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);
SendAndUpdateFilamentSensor();
return ButtonPushed;
case ResponseMsgParamCodes::Finished:
progressCode = ProgressCode::OK;
scopeState = ScopeState::Ready;
return Finished;
default:
return ProtocolError;
} }
return Finished;
} }
StepStatus ProtocolLogic::CommandStep() { StepStatus ProtocolLogic::CommandStep() {
switch (scopeState) { 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: { case ScopeState::CommandSent: {
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
return expmsg;
switch (rsp.paramCode) { // the response should be either accepted or rejected switch (rsp.paramCode) { // the response should be either accepted or rejected
case ResponseMsgParamCodes::Accepted: case ResponseMsgParamCodes::Accepted:
progressCode = ProgressCode::OK; progressCode = ProgressCode::OK;
@ -275,59 +297,21 @@ StepStatus ProtocolLogic::CommandStep() {
default: default:
return ProtocolError; return ProtocolError;
} }
} break; } break;
case ScopeState::QuerySent: case ScopeState::QuerySent:
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady) return ProcessCommandQueryResponse();
return expmsg;
[[fallthrough]];
case ScopeState::ContinueFromIdle:
switch (rsp.paramCode) {
case ResponseMsgParamCodes::Processing:
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
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();
return CommandError;
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);
SendAndUpdateFilamentSensor();
return ButtonPushed;
case ResponseMsgParamCodes::Finished:
progressCode = ProgressCode::OK;
scopeState = ScopeState::Ready;
return Finished;
default:
return ProtocolError;
}
break;
case ScopeState::FilamentSensorStateSent: case ScopeState::FilamentSensorStateSent:
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
return expmsg;
SendFINDAQuery(); SendFINDAQuery();
scopeState = ScopeState::FINDAReqSent; scopeState = ScopeState::FINDAReqSent;
return Processing; return Processing;
case ScopeState::FINDAReqSent: case ScopeState::FINDAReqSent:
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady) SendReadRegister(4, ScopeState::StatisticsSent);
return expmsg;
SendReadRegister(3, ScopeState::StatisticsSent);
scopeState = ScopeState::StatisticsSent; scopeState = ScopeState::StatisticsSent;
return Processing; return Processing;
case ScopeState::StatisticsSent: case ScopeState::StatisticsSent:
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
return expmsg;
scopeState = ScopeState::Wait; scopeState = ScopeState::Wait;
return Processing; return Processing;
case ScopeState::ButtonSent: case ScopeState::ButtonSent:
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady)
return expmsg;
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) { if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
// Button was accepted, decrement the retry. // Button was accepted, decrement the retry.
mmu2.DecrementRetryAttempts(); mmu2.DecrementRetryAttempts();
@ -340,83 +324,82 @@ StepStatus ProtocolLogic::CommandStep() {
return Processing; return Processing;
} }
StepStatus ProtocolLogic::IdleStep() { StepStatus ProtocolLogic::IdleWait() {
if(scopeState == ScopeState::Ready){ // check timeout if (scopeState == ScopeState::Ready) { // check timeout
if (Elapsed(heartBeatPeriod)) { if (Elapsed(heartBeatPeriod)) {
SendQuery(); SendQuery();
return Processing; return Processing;
} }
} else { }
if (auto expmsg = ExpectingMessage(linkLayerTimeout); expmsg != MessageReady) return Finished;
return expmsg; }
switch (scopeState) { StepStatus ProtocolLogic::IdleStep() {
case ScopeState::QuerySent: // check UART switch (scopeState) {
// 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 ScopeState::QuerySent: // check UART
// That causes no issues here, we just need to switch to Command processing and continue there from now on. // 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.
// 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. // That causes no issues here, we just need to switch to Command processing and continue there from now on.
switch( rsp.request.code ){ // 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::Cut: switch (rsp.request.code) {
case RequestMsgCodes::Eject: case RequestMsgCodes::Cut:
case RequestMsgCodes::Load: case RequestMsgCodes::Eject:
case RequestMsgCodes::Mode: case RequestMsgCodes::Load:
case RequestMsgCodes::Tool: case RequestMsgCodes::Mode:
case RequestMsgCodes::Unload: case RequestMsgCodes::Tool:
if( rsp.paramCode != ResponseMsgParamCodes::Finished ){ case RequestMsgCodes::Unload:
SwitchFromIdleToCommand(); if (rsp.paramCode != ResponseMsgParamCodes::Finished) {
return Processing; return SwitchFromIdleToCommand();
} }
break; break;
case RequestMsgCodes::Reset: case RequestMsgCodes::Reset:
// this one is kind of special // this one is kind of special
// we do not transfer to any "running" command (i.e. we stay in Idle), // 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 // but in case there is an error reported we must make sure it gets propagated
switch( rsp.paramCode ){ switch (rsp.paramCode) {
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.
buttonCode = static_cast<Buttons>(rsp.paramValue); buttonCode = static_cast<Buttons>(rsp.paramValue);
SendFINDAQuery(); SendFINDAQuery();
return ButtonPushed; return ButtonPushed;
case ResponseMsgParamCodes::Processing: case ResponseMsgParamCodes::Processing:
// @@TODO we may actually use this branch to report progress of manual operation on the MMU // @@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 // 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" // For now let's behave just like "finished"
case ResponseMsgParamCodes::Finished: case ResponseMsgParamCodes::Finished:
errorCode = ErrorCode::OK; 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:
return ProtocolError; errorCode = static_cast<ErrorCode>(rsp.paramValue);
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 ScopeState::FINDAReqSent:
SendReadRegister(4, ScopeState::StatisticsSent);
scopeState = ScopeState::StatisticsSent;
return Processing;
case ScopeState::StatisticsSent:
failStatistics = rsp.paramValue;
scopeState = ScopeState::Ready;
return Finished;
case ScopeState::ButtonSent:
if (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.
@ -443,9 +426,7 @@ ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
, lastFSensor((uint8_t)WhereIsFilament()) , lastFSensor((uint8_t)WhereIsFilament())
, findaPressed(false) , findaPressed(false)
, failStatistics(0) , failStatistics(0)
, mmuFwVersionMajor(0) , mmuFwVersion { 0, 0, 0 }
, mmuFwVersionMinor(0)
, mmuFwVersionBuild(0)
{} {}
void ProtocolLogic::Start() { void ProtocolLogic::Start() {
@ -480,7 +461,7 @@ void ProtocolLogic::EjectFilament(uint8_t slot) {
PlanGenericRequest(RequestMsg(RequestMsgCodes::Eject, slot)); PlanGenericRequest(RequestMsg(RequestMsgCodes::Eject, slot));
} }
void ProtocolLogic::CutFilament(uint8_t slot){ void ProtocolLogic::CutFilament(uint8_t slot) {
PlanGenericRequest(RequestMsg(RequestMsgCodes::Cut, slot)); PlanGenericRequest(RequestMsg(RequestMsgCodes::Cut, slot));
} }
@ -488,28 +469,28 @@ void ProtocolLogic::ResetMMU() {
PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, 0)); PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, 0));
} }
void ProtocolLogic::Button(uint8_t index){ void ProtocolLogic::Button(uint8_t index) {
PlanGenericRequest(RequestMsg(RequestMsgCodes::Button, index)); PlanGenericRequest(RequestMsg(RequestMsgCodes::Button, index));
} }
void ProtocolLogic::Home(uint8_t mode){ void ProtocolLogic::Home(uint8_t mode) {
PlanGenericRequest(RequestMsg(RequestMsgCodes::Home, mode)); PlanGenericRequest(RequestMsg(RequestMsgCodes::Home, mode));
} }
void ProtocolLogic::PlanGenericRequest(RequestMsg rq) { void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
plannedRq = rq; plannedRq = rq;
if( ! 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
} }
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
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) {
currentScope = Scope::Command; currentScope = Scope::Command;
SetRequestMsg(plannedRq); SetRequestMsg(plannedRq);
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0); plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
@ -519,12 +500,12 @@ bool ProtocolLogic::ActivatePlannedRequest(){
return false; return false;
} }
void ProtocolLogic::SwitchFromIdleToCommand(){ StepStatus ProtocolLogic::SwitchFromIdleToCommand() {
currentScope = Scope::Command; currentScope = Scope::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
CommandContinueFromIdle(); return ProcessCommandQueryResponse();
} }
void ProtocolLogic::SwitchToIdle() { void ProtocolLogic::SwitchToIdle() {
@ -533,7 +514,7 @@ void ProtocolLogic::SwitchToIdle() {
IdleRestart(); IdleRestart();
} }
void ProtocolLogic::SwitchFromStartToIdle(){ void ProtocolLogic::SwitchFromStartToIdle() {
state = State::Running; state = State::Running;
currentScope = Scope::Idle; currentScope = Scope::Idle;
IdleRestart(); IdleRestart();
@ -541,24 +522,6 @@ void ProtocolLogic::SwitchFromStartToIdle(){
scopeState = ScopeState::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 {
return _millis() >= (lastUARTActivityMs + timeout); return _millis() >= (lastUARTActivityMs + timeout);
} }
@ -567,12 +530,12 @@ void ProtocolLogic::RecordUARTActivity() {
lastUARTActivityMs = _millis(); lastUARTActivityMs = _millis();
} }
void ProtocolLogic::RecordReceivedByte(uint8_t c){ void ProtocolLogic::RecordReceivedByte(uint8_t c) {
lastReceivedBytes[lrb] = c; lastReceivedBytes[lrb] = c;
lrb = (lrb+1) % lastReceivedBytes.size(); lrb = (lrb + 1) % lastReceivedBytes.size();
} }
constexpr char NibbleToChar(uint8_t c){ constexpr char NibbleToChar(uint8_t c) {
switch (c) { switch (c) {
case 0: case 0:
case 1: case 1:
@ -597,42 +560,46 @@ constexpr char NibbleToChar(uint8_t c){
} }
} }
void ProtocolLogic::FormatLastReceivedBytes(char *dst){ void ProtocolLogic::FormatLastReceivedBytes(char *dst) {
for(uint8_t i = 0; i < lastReceivedBytes.size(); ++i){ for (uint8_t i = 0; i < lastReceivedBytes.size(); ++i) {
uint8_t b = lastReceivedBytes[ (lrb-i-1) % lastReceivedBytes.size() ]; uint8_t b = lastReceivedBytes[(lrb - i - 1) % lastReceivedBytes.size()];
dst[i*3] = NibbleToChar(b >> 4); dst[i * 3] = NibbleToChar(b >> 4);
dst[i*3+1] = NibbleToChar(b & 0xf); dst[i * 3 + 1] = NibbleToChar(b & 0xf);
dst[i*3+2] = ' '; dst[i * 3 + 2] = ' ';
} }
dst[ (lastReceivedBytes.size() - 1) * 3 + 2] = 0; // terminate properly dst[(lastReceivedBytes.size() - 1) * 3 + 2] = 0; // terminate properly
} }
void ProtocolLogic::FormatLastResponseMsgAndClearLRB(char *dst){ void ProtocolLogic::FormatLastResponseMsgAndClearLRB(char *dst) {
*dst++ = '<'; *dst++ = '<';
for(uint8_t i = 0; i < lrb; ++i){ for (uint8_t i = 0; i < lrb; ++i) {
uint8_t b = lastReceivedBytes[ i ]; uint8_t b = lastReceivedBytes[i];
if( b < 32 )b = '.'; if (b < 32)
if( b > 127 )b = '.'; b = '.';
if (b > 127)
b = '.';
*dst++ = b; *dst++ = b;
} }
*dst = 0; // terminate properly *dst = 0; // terminate properly
lrb = 0; // reset the input buffer index in case of a clean message lrb = 0; // reset the input buffer index in case of a clean message
} }
void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size){ void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size) {
constexpr uint_fast8_t rqs = modules::protocol::Protocol::MaxRequestSize() + 2; constexpr uint_fast8_t rqs = modules::protocol::Protocol::MaxRequestSize() + 2;
char tmp[rqs] = ">"; char tmp[rqs] = ">";
static char lastMsg[rqs] = ""; static char lastMsg[rqs] = "";
for(uint8_t i = 0; i < size; ++i){ for (uint8_t i = 0; i < size; ++i) {
uint8_t b = txbuff[i]; uint8_t b = txbuff[i];
if( b < 32 )b = '.'; if (b < 32)
if( b > 127 )b = '.'; b = '.';
tmp[i+1] = b; if (b > 127)
b = '.';
tmp[i + 1] = b;
} }
tmp[size+1] = '\n'; tmp[size + 1] = '\n';
tmp[size+2] = 0; tmp[size + 2] = 0;
if( !strncmp_P(tmp, PSTR(">S0*99.\n"), rqs) && !strncmp(lastMsg, tmp, rqs) ){ if (!strncmp_P(tmp, PSTR(">S0*99.\n"), rqs) && !strncmp(lastMsg, tmp, rqs)) {
// @@TODO we skip the repeated request msgs for now // @@TODO we skip the repeated request msgs for now
// to avoid spoiling the whole log just with ">S0" messages // to avoid spoiling the whole log just with ">S0" messages
// especially when the MMU is not connected. // especially when the MMU is not connected.
// We'll lose the ability to see if the printer is actually // We'll lose the ability to see if the printer is actually
@ -644,16 +611,16 @@ void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size){
memcpy(lastMsg, tmp, rqs); memcpy(lastMsg, tmp, rqs);
} }
void ProtocolLogic::LogError(const char *reason_P){ void ProtocolLogic::LogError(const char *reason_P) {
char lrb[lastReceivedBytes.size() * 3]; char lrb[lastReceivedBytes.size() * 3];
FormatLastReceivedBytes(lrb); FormatLastReceivedBytes(lrb);
MMU2_ERROR_MSGRPGM(reason_P); MMU2_ERROR_MSGRPGM(reason_P);
SERIAL_ECHOPGM(", last bytes: "); SERIAL_ECHOPGM(", last bytes: ");
SERIAL_ECHOLN(lrb); SERIAL_ECHOLN(lrb);
} }
void ProtocolLogic::LogResponse(){ void ProtocolLogic::LogResponse() {
char lrb[lastReceivedBytes.size()]; char lrb[lastReceivedBytes.size()];
FormatLastResponseMsgAndClearLRB(lrb); FormatLastResponseMsgAndClearLRB(lrb);
MMU2_ECHO_MSG(lrb); MMU2_ECHO_MSG(lrb);
@ -661,7 +628,7 @@ void ProtocolLogic::LogResponse(){
} }
StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg_P, StepStatus ss) { StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg_P, StepStatus ss) {
if( dataTO.Record(ss) ){ if (dataTO.Record(ss)) {
LogError(msg_P); LogError(msg_P);
return dataTO.InitialCause(); return dataTO.InitialCause();
} else { } else {
@ -685,7 +652,7 @@ StepStatus ProtocolLogic::HandleProtocolError() {
} }
StepStatus ProtocolLogic::Step() { StepStatus ProtocolLogic::Step() {
if( ! 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 = ScopeStep(); auto currentStatus = ScopeStep();
@ -697,12 +664,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 = currentScope == Scope::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 && currentScope == Scope::Command){ if (!previousCommandFinished && currentScope == Scope::Command) {
currentStatus = Processing; currentStatus = Processing;
} }
} }
@ -736,13 +703,13 @@ StepStatus ProtocolLogic::Step() {
} }
uint8_t ProtocolLogic::CommandInProgress() const { uint8_t ProtocolLogic::CommandInProgress() const {
if( currentScope != Scope::Command ) if (currentScope != Scope::Command)
return 0; return 0;
return (uint8_t)ReqMsg().code; return (uint8_t)ReqMsg().code;
} }
bool DropOutFilter::Record(StepStatus ss){ bool DropOutFilter::Record(StepStatus ss) {
if( occurrences == maxOccurrences ){ if (occurrences == maxOccurrences) {
cause = ss; cause = ss;
} }
--occurrences; --occurrences;

View File

@ -34,20 +34,19 @@ enum StepStatus : uint_fast8_t {
MessageReady, ///< a message has been successfully decoded from the received bytes MessageReady, ///< a message has been successfully decoded from the received bytes
Finished, Finished,
CommunicationTimeout, ///< the MMU failed to respond to a request within a specified time frame CommunicationTimeout, ///< the MMU failed to respond to a request within a specified time frame
ProtocolError, ///< bytes read from the MMU didn't form a valid response ProtocolError, ///< bytes read from the MMU didn't form a valid response
CommandRejected, ///< the MMU rejected the command due to some other command in progress, may be the user is operating the MMU locally (button commands) CommandRejected, ///< the MMU rejected the command due to some other command in progress, may be the user is operating the MMU locally (button commands)
CommandError, ///< the command in progress stopped due to unrecoverable error, user interaction required CommandError, ///< the command in progress stopped due to unrecoverable error, user interaction required
VersionMismatch, ///< the MMU reports its firmware version incompatible with our implementation VersionMismatch, ///< the MMU reports its firmware version incompatible with our implementation
CommunicationRecovered, CommunicationRecovered,
ButtonPushed, ///< The MMU reported the user pushed one of its three buttons. ButtonPushed, ///< The MMU reported the user pushed one of its three buttons.
}; };
static constexpr uint32_t linkLayerTimeout = 2000; ///< default link layer communication timeout
static constexpr uint32_t linkLayerTimeout = 2000; ///< default link layer communication timeout
static constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; ///< data layer communication timeout static constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; ///< data layer communication timeout
static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of heart beat messages (Q0) static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of heart beat messages (Q0)
static_assert( heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts"); static_assert(heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts");
///< Filter of short consecutive drop outs which are recovered instantly ///< Filter of short consecutive drop outs which are recovered instantly
class DropOutFilter { class DropOutFilter {
@ -55,17 +54,17 @@ class DropOutFilter {
uint8_t occurrences; uint8_t occurrences;
public: public:
static constexpr uint8_t maxOccurrences = 10; // ideally set this to >8 seconds -> 12x heartBeatPeriod static constexpr uint8_t maxOccurrences = 10; // ideally set this to >8 seconds -> 12x heartBeatPeriod
static_assert (maxOccurrences > 1, "we should really silently ignore at least 1 comm drop out if recovered immediately afterwards"); static_assert(maxOccurrences > 1, "we should really silently ignore at least 1 comm drop out if recovered immediately afterwards");
DropOutFilter() = default; DropOutFilter() = default;
/// @returns true if the error should be reported to higher levels (max. number of consecutive occurrences reached) /// @returns true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
bool Record(StepStatus ss); bool Record(StepStatus ss);
/// @returns the initial cause which started this drop out event /// @returns the initial cause which started this drop out event
inline StepStatus InitialCause()const { return cause; } inline StepStatus InitialCause() const { return cause; }
/// Rearms the object for further processing - basically call this once the MMU responds with something meaningful (e.g. S0 A2) /// Rearms the object for further processing - basically call this once the MMU responds with something meaningful (e.g. S0 A2)
inline void Reset(){ occurrences = maxOccurrences; } inline void Reset() { occurrences = maxOccurrences; }
}; };
/// Logic layer of the MMU vs. printer communication protocol /// Logic layer of the MMU vs. printer communication protocol
@ -98,13 +97,13 @@ public:
/// @returns the current/latest process code as reported by the MMU /// @returns the current/latest process code as reported by the MMU
ProgressCode Progress() const { return progressCode; } ProgressCode Progress() const { return progressCode; }
/// @returns the current/latest button code as reported by the MMU /// @returns the current/latest button code as reported by the MMU
Buttons Button() const { return buttonCode; } Buttons Button() const { return buttonCode; }
uint8_t CommandInProgress()const; uint8_t CommandInProgress() const;
inline bool Running()const { inline bool Running() const {
return state == State::Running; return state == State::Running;
} }
@ -117,20 +116,20 @@ public:
} }
inline uint8_t MmuFwVersionMajor() const { inline uint8_t MmuFwVersionMajor() const {
return mmuFwVersionMajor; return mmuFwVersion[0];
} }
inline uint8_t MmuFwVersionMinor() const { inline uint8_t MmuFwVersionMinor() const {
return mmuFwVersionMinor; return mmuFwVersion[1];
} }
inline uint8_t MmuFwVersionBuild() const { inline uint8_t MmuFwVersionRevision() const {
return mmuFwVersionBuild; return mmuFwVersion[2];
} }
#ifndef UNITTEST #ifndef UNITTEST
private: private:
#endif #endif
StepStatus ExpectingMessage(uint32_t timeout); StepStatus ExpectingMessage();
void SendMsg(RequestMsg rq); void SendMsg(RequestMsg rq);
void SwitchToIdle(); void SwitchToIdle();
StepStatus SuppressShortDropOuts(const char *msg_P, StepStatus ss); StepStatus SuppressShortDropOuts(const char *msg_P, StepStatus ss);
@ -144,9 +143,9 @@ private:
void LogRequestMsg(const uint8_t *txbuff, uint8_t size); void LogRequestMsg(const uint8_t *txbuff, uint8_t size);
void LogError(const char *reason_P); void LogError(const char *reason_P);
void LogResponse(); void LogResponse();
void SwitchFromIdleToCommand(); StepStatus SwitchFromIdleToCommand();
void SwitchFromStartToIdle(); void SwitchFromStartToIdle();
enum class State : uint_fast8_t { enum class State : uint_fast8_t {
Stopped, ///< stopped for whatever reason Stopped, ///< stopped for whatever reason
InitSequence, ///< initial sequence running InitSequence, ///< initial sequence running
@ -170,17 +169,14 @@ private:
Command Command
}; };
Scope currentScope; Scope currentScope;
// basic scope members // basic scope members
/// @returns true if the state machine is waiting for a response from the MMU /// @returns true if the state machine is waiting for a response from the MMU
bool ExpectsResponse()const { return scopeState != ScopeState::Ready && scopeState != ScopeState::Wait; } bool ExpectsResponse() const { return ((uint8_t)scopeState & (uint8_t)ScopeState::NotExpectsResponse) == 0; }
/// Common internal states of the derived sub-automata /// Common internal states of the derived sub-automata
/// General rule of thumb: *Sent states are waiting for a response from the MMU /// General rule of thumb: *Sent states are waiting for a response from the MMU
enum class ScopeState : uint_fast8_t { enum class ScopeState : uint_fast8_t {
Ready,
Wait,
S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another
S1Sent, S1Sent,
S2Sent, S2Sent,
@ -191,23 +187,26 @@ private:
FINDAReqSent, FINDAReqSent,
StatisticsSent, StatisticsSent,
ButtonSent, ButtonSent,
ContinueFromIdle, // States which do not expect a message - MSb set
RecoveringProtocolError NotExpectsResponse = 0x80,
Wait = NotExpectsResponse + 1,
Ready = NotExpectsResponse + 2,
RecoveringProtocolError = NotExpectsResponse + 3,
}; };
ScopeState scopeState; ///< internal state of the sub-automaton ScopeState scopeState; ///< internal state of the sub-automaton
/// @returns the status of processing of the FINDA query response /// @returns the status of processing of the FINDA query response
/// @param finishedRV returned value in case the message was successfully received and processed /// @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 /// @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); // StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
/// @returns the status of processing of the statistics query response /// @returns the status of processing of the statistics query response
/// @param finishedRV returned value in case the message was successfully received and processed /// @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 /// @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); // StepStatus ProcessStatisticsReqSent(StepStatus finishedRV, State nextState);
/// Called repeatedly while waiting for a query (Q0) period. /// 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. /// 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. /// So far, the only such a case is the filament sensor, but there can be more like this in the future.
@ -218,42 +217,45 @@ private:
void SendButton(uint8_t btn); void SendButton(uint8_t btn);
void SendVersion(uint8_t stage); void SendVersion(uint8_t stage);
void SendReadRegister(uint8_t index, ScopeState nextState); void SendReadRegister(uint8_t index, ScopeState nextState);
StepStatus ProcessVersionResponse(uint8_t stage);
/// Top level split - calls the appropriate step based on current scope /// Top level split - calls the appropriate step based on current scope
StepStatus ScopeStep(); StepStatus ScopeStep();
static constexpr uint8_t maxRetries = 6; static constexpr uint8_t maxRetries = 6;
uint8_t retries; uint8_t retries;
void StartSeqRestart(); void StartSeqRestart();
void DelayedRestartRestart(); void DelayedRestartRestart();
void IdleRestart(); void IdleRestart();
void CommandRestart(); void CommandRestart();
StepStatus StartSeqStep(); StepStatus StartSeqStep();
StepStatus DelayedRestartStep(); StepStatus DelayedRestartWait();
StepStatus IdleStep(); StepStatus IdleStep();
StepStatus IdleWait();
StepStatus CommandStep(); StepStatus CommandStep();
StepStatus StoppedStep(){ return Processing; } StepStatus CommandWait();
StepStatus StoppedStep() { return Processing; }
StepStatus ProcessCommandQueryResponse();
inline void SetRequestMsg(RequestMsg msg) { inline void SetRequestMsg(RequestMsg msg) {
rq = msg; rq = msg;
} }
void CommandContinueFromIdle(){ inline const RequestMsg &ReqMsg() const { return rq; }
scopeState = ScopeState::ContinueFromIdle;
}
inline const RequestMsg &ReqMsg()const { return rq; }
RequestMsg rq = RequestMsg(RequestMsgCodes::unknown, 0); 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.
/// It exists solely to prevent breaking the Request-Response protocol handshake - /// It exists solely to prevent breaking the Request-Response protocol handshake -
/// - during tests it turned out, that the commands from Marlin are coming in such an asynchronnous way, that /// - during tests it turned out, that the commands from Marlin are coming in such an asynchronnous way, that
/// we could accidentally send T2 immediately after Q0 without waiting for reception of response to Q0. /// we could accidentally send T2 immediately after Q0 without waiting for reception of response to Q0.
/// ///
/// Beware, if Marlin manages to call PlanGenericCommand multiple times before a response comes, /// Beware, if Marlin manages to call PlanGenericCommand multiple times before a response comes,
/// these variables will get overwritten by the last call. /// these variables will get overwritten by the last call.
/// However, that should not happen under normal circumstances as Marlin should wait for the Command to finish, /// However, that should not happen under normal circumstances as Marlin should wait for the Command to finish,
/// which includes all responses (and error recovery if any). /// which includes all responses (and error recovery if any).
RequestMsg plannedRq; RequestMsg plannedRq;
@ -263,7 +265,7 @@ private:
bool ActivatePlannedRequest(); bool ActivatePlannedRequest();
uint32_t lastUARTActivityMs; ///< timestamp - last ms when something occurred on the UART uint32_t lastUARTActivityMs; ///< timestamp - last ms when something occurred on the UART
DropOutFilter dataTO; ///< Filter of short consecutive drop outs which are recovered instantly DropOutFilter dataTO; ///< Filter of short consecutive drop outs which are recovered instantly
ResponseMsg rsp; ///< decoded response message from the MMU protocol ResponseMsg rsp; ///< decoded response message from the MMU protocol
@ -285,8 +287,8 @@ private:
bool findaPressed; bool findaPressed;
uint16_t failStatistics; uint16_t failStatistics;
uint8_t mmuFwVersionMajor, mmuFwVersionMinor; uint8_t mmuFwVersion[3];
uint8_t mmuFwVersionBuild; uint16_t mmuFwVersionBuild;
friend class ProtocolLogicPartBase; friend class ProtocolLogicPartBase;
friend class Stopped; friend class Stopped;