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:
parent
dfddf3eaa5
commit
05ad1dc2f6
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue