1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-08-30 09:39:49 +02:00

Made some changes to the way error handling works. ConfigAlarm is now more rigid; you just cannot get out. Pin and error handling should be better, and paths from the hierarchy are emitted.

This commit is contained in:
Stefan de Bruijn
2021-06-29 11:24:51 +02:00
parent 4f09112ad4
commit 2daad23e04
16 changed files with 151 additions and 60 deletions

View File

@@ -19,12 +19,27 @@
#include "AfterParse.h"
#include "Configurable.h"
#include "../System.h"
#include "../Logging.h"
#include <cstring>
namespace Configuration {
void AfterParse::enterSection(const char* name, Configurable* value) {
value->afterParse();
_path.push_back(name); // For error handling
try {
value->afterParse();
} catch (const AssertionFailed& ex) {
// Log something meaningful to the user:
log_error("Initialization error at "; for (auto it : _path) { ss << '/' << it; } ss << ": " << ex.msg);
// Set the state to config alarm, so users can't run time machine.
sys.state = State::ConfigAlarm;
}
value->group(*this);
_path.erase(_path.begin() + (_path.size() - 1));
}
}

View File

@@ -18,11 +18,11 @@
#pragma once
#include <vector>
#include "../Pin.h"
#include "HandlerBase.h"
#include <vector>
namespace Configuration {
class Configurable;
@@ -30,6 +30,8 @@ namespace Configuration {
AfterParse(const AfterParse&) = delete;
AfterParse& operator=(const AfterParse&) = delete;
std::vector<const char*> _path;
protected:
void enterSection(const char* name, Configurable* value) override;
bool matchesUninitialized(const char* name) override { return false; }

View File

@@ -24,15 +24,20 @@
#include "../Logging.h"
#include <vector>
// #define DEBUG_VERBOSE_YAML_PARSER
// #define DEBUG_CHATTY_YAML_PARSER
namespace Configuration {
class ParserHandler : public Configuration::HandlerBase {
private:
Configuration::Parser& _parser;
Configuration::Parser& _parser;
std::vector<const char*> _path;
public:
void enterSection(const char* name, Configuration::Configurable* section) override {
_path.push_back(name); // For error handling
// On entry, the token is for the section that invoked us.
// We will handle following nodes with indents greater than entryIndent
int entryIndent = _parser.token_.indent_;
@@ -66,7 +71,16 @@ namespace Configuration {
#ifdef DEBUG_VERBOSE_YAML_PARSER
log_debug("Parsing key " << _parser.key().str());
#endif
section->group(*this);
try {
section->group(*this);
} catch (const AssertionFailed& ex) {
// Log something meaningful to the user:
log_error("Configuration error at "; for (auto it : _path) { ss << '/' << it; } ss << ": " << ex.msg);
// Set the state to config alarm, so users can't run time machine.
sys.state = State::ConfigAlarm;
}
if (_parser.token_.state == TokenState::Matching) {
log_error("Ignored key " << _parser.key().str());
}
@@ -90,6 +104,8 @@ namespace Configuration {
#ifdef DEBUG_CHATTY_YAML_PARSER
log_debug("Left section at indent " << entryIndent << " holding " << _parser.key().str());
#endif
_path.erase(_path.begin() + (_path.size() - 1));
}
bool matchesUninitialized(const char* name) override { return _parser.is(name); }

View File

@@ -19,6 +19,8 @@
#include "Validator.h"
#include "Configurable.h"
#include "../System.h"
#include "../Logging.h"
#include <cstring>
#include <atomic>
@@ -30,7 +32,20 @@ namespace Configuration {
}
void Validator::enterSection(const char* name, Configurable* value) {
value->validate();
_path.push_back(name); // For error handling
try {
value->validate();
} catch (const AssertionFailed& ex) {
// Log something meaningful to the user:
log_error("Validation error at "; for (auto it : _path) { ss << '/' << it; } ss << ": " << ex.msg);
// Set the state to config alarm, so users can't run time machine.
sys.state = State::ConfigAlarm;
}
value->group(*this);
_path.erase(_path.begin() + (_path.size() - 1));
}
}

View File

@@ -18,11 +18,11 @@
#pragma once
#include <vector>
#include "../Pin.h"
#include "HandlerBase.h"
#include <vector>
namespace Configuration {
class Configurable;
@@ -30,6 +30,8 @@ namespace Configuration {
Validator(const Validator&) = delete;
Validator& operator=(const Validator&) = delete;
std::vector<const char*> _path;
protected:
void enterSection(const char* name, Configurable* value) override;
bool matchesUninitialized(const char* name) override { return false; }

View File

@@ -82,4 +82,5 @@ std::map<Error, const char*> ErrorNames = {
{ Error::AnotherInterfaceBusy, "Another interface is busy" },
{ Error::BadPinSpecification, "Bad Pin Specification" },
{ Error::JogCancelled, "Jog Cancelled" },
{ Error::ConfigurationInvalid, "Configuration is invalid. Check boot messages for ERR's." },
};

View File

@@ -89,6 +89,7 @@ enum class Error : uint8_t {
JogCancelled = 130,
BadPinSpecification = 150,
BadRuntimeConfigSetting = 151,
ConfigurationInvalid = 152,
};
extern std::map<Error, const char*> ErrorNames;

View File

@@ -113,26 +113,27 @@ void grbl_init() {
} else {
sys.state = State::Idle;
}
}
// Check for power-up and set system alarm if homing is enabled to force homing cycle
// by setting Grbl's alarm state. Alarm locks out all g-code commands, including the
// startup scripts, but allows access to settings and internal commands. Only a homing
// cycle '$H' or kill alarm locks '$X' will disable the alarm.
// NOTE: The startup script will run after successful completion of the homing cycle, but
// not after disabling the alarm locks. Prevents motion startup blocks from crashing into
// things uncontrollably. Very bad.
if (config->_homingInitLock && homingAxes() && sys.state != State::ConfigAlarm) {
// If there is an axis with homing configured, enter Alarm state on startup
sys.state = State::Alarm;
}
for (auto s : config->_spindles) {
s->init();
}
Spindles::Spindle::switchSpindle(0, config->_spindles, spindle);
config->_coolant->init();
limits_init();
config->_probe->init();
// Check for power-up and set system alarm if homing is enabled to force homing cycle
// by setting Grbl's alarm state. Alarm locks out all g-code commands, including the
// startup scripts, but allows access to settings and internal commands. Only a homing
// cycle '$H' or kill alarm locks '$X' will disable the alarm.
// NOTE: The startup script will run after successful completion of the homing cycle, but
// not after disabling the alarm locks. Prevents motion startup blocks from crashing into
// things uncontrollably. Very bad.
if (config->_homingInitLock && homingAxes()) {
// If there is an axis with homing configured, enter Alarm state on startup
sys.state = State::Alarm;
}
for (auto s : config->_spindles) {
s->init();
}
Spindles::Spindle::switchSpindle(0, config->_spindles, spindle);
config->_coolant->init();
limits_init();
config->_probe->init();
}
WebUI::wifi_config.begin();
if (config->_comms->_bluetoothConfig) {
@@ -155,11 +156,16 @@ static void reset_variables() {
// or by the post-configuration fixup, but we test
// it anyway just for safety. We want to avoid any
// possibility of crashing at this point.
if (spindle) {
spindle->stop();
}
plan_reset(); // Clear block buffer and planner variables
st_reset(); // Clear stepper subsystem variables
if (sys.state != State::ConfigAlarm) {
if (spindle) {
spindle->stop();
}
st_reset(); // Clear stepper subsystem variables
}
// Sync cleared gcode and planner positions to current system position.
plan_sync_position();
gc_sync_position();
@@ -189,6 +195,7 @@ void run_once() {
// require careful teardown of the existing configuration
// to avoid memory leaks. It is probably worth doing eventually.
error_all("Critical error in run_once: %s", ex.msg.c_str());
error_all("Stacktrace: %s", ex.stackTrace.c_str());
sys.state = State::ConfigAlarm;
}
if (++tries > 1) {

View File

@@ -276,7 +276,7 @@ namespace Machine {
// log_info("Heap size after configuation load is " << uint32_t(xPortGetFreeHeapSize()));
successful = true;
successful = (sys.state != State::ConfigAlarm);
} catch (const Configuration::ParseException& ex) {
sys.state = State::ConfigAlarm;

View File

@@ -158,10 +158,17 @@ Pin Pin::create(const StringRange& str) {
return Pin(pinImplementation);
}
} catch (const AssertionFailed& ex) { // We shouldn't get here under normal circumstances.
char buf[255];
snprintf(buf, 255, "ERR: Setting up pin [%s] failed. Details: %s", str.str().c_str(), ex.what());
Assert(false, buf);
/*
pin_error("ERR: Setting up pin [%s] failed. Details: %s", str.str().c_str(), ex.what());
(void)ex; // Get rid of compiler warning
return Pin(new Pins::ErrorPinDetail(str.str()));
*/
}
}

View File

@@ -51,5 +51,5 @@ namespace Pins {
PinAttributes ErrorPinDetail::getAttr() const { return PinAttributes::None; }
String ErrorPinDetail::toString() { return "ERROR_PIN"; }
String ErrorPinDetail::toString() { return "ERROR_PIN (for " + _description + ")"; }
}

View File

@@ -103,6 +103,10 @@ void settings_init() {
// than actual settings, which change infrequently, so handling
// it early is probably prudent.
Error jog_set(uint8_t* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {
if (sys.state == State::ConfigAlarm) {
return Error::ConfigurationInvalid;
}
// Execute only if in IDLE or JOG states.
if (sys.state != State::Idle && sys.state != State::Jog) {
return Error::IdleError;
@@ -217,6 +221,10 @@ Error list_commands(const char* value, WebUI::AuthenticationLevel auth_level, We
return Error::Ok;
}
Error toggle_check_mode(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {
if (sys.state == State::ConfigAlarm) {
return Error::ConfigurationInvalid;
}
// Perform reset when toggling off. Check g-code mode should only work if Grbl
// is idle and ready, regardless of alarm locks. This is mainly to keep things
// simple and consistent.
@@ -234,7 +242,9 @@ Error toggle_check_mode(const char* value, WebUI::AuthenticationLevel auth_level
return Error::Ok;
}
Error disable_alarm_lock(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {
if (sys.state == State::Alarm || sys.state == State::ConfigAlarm) {
if (sys.state == State::ConfigAlarm) {
return Error::ConfigurationInvalid;
} else if (sys.state == State::Alarm) {
// Block if safety door is ajar.
if (config->_control->system_check_safety_door_ajar()) {
return Error::CheckDoor;
@@ -250,6 +260,10 @@ Error report_ngc(const char* value, WebUI::AuthenticationLevel auth_level, WebUI
return Error::Ok;
}
Error home(int cycle) {
if (sys.state == State::ConfigAlarm) {
return Error::ConfigurationInvalid;
}
if (!homingAxes()) {
return Error::SettingDisabled;
}
@@ -346,6 +360,10 @@ Error showState(const char* value, WebUI::AuthenticationLevel auth_level, WebUI:
}
Error doJog(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {
if (sys.state == State::ConfigAlarm) {
return Error::ConfigurationInvalid;
}
// For jogging, you must give gc_execute_line() a line that
// begins with $J=. There are several ways we can get here,
// including $J, $J=xxx, [J]xxx. For any form other than
@@ -421,6 +439,10 @@ Error listErrors(const char* value, WebUI::AuthenticationLevel auth_level, WebUI
}
Error motor_disable(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {
if (sys.state == State::ConfigAlarm) {
return Error::ConfigurationInvalid;
}
while (value && isspace(*value)) {
++value;
}
@@ -556,7 +578,7 @@ Error do_command_or_setting(const char* key, char* value, WebUI::AuthenticationL
config->group(validator);
} catch (std::exception& ex) {
log_error("Validation error: " << ex.what());
return Error::BadRuntimeConfigSetting;
return Error::ConfigurationInvalid;
}
Configuration::AfterParse afterParseHandler;
@@ -567,10 +589,10 @@ Error do_command_or_setting(const char* key, char* value, WebUI::AuthenticationL
}
} catch (const Configuration::ParseException& ex) {
log_error("Configuration parse error: " << ex.What() << " @ " << ex.LineNumber() << ":" << ex.ColumnNumber());
return Error::BadRuntimeConfigSetting;
return Error::ConfigurationInvalid;
} catch (const AssertionFailed& ex) {
log_error("Configuration change failed: " << ex.what());
return Error::BadRuntimeConfigSetting;
return Error::ConfigurationInvalid;
}
// Next search the settings list by text name. If found, set a new

View File

@@ -132,32 +132,35 @@ void protocol_main_loop() {
empty_lines();
//uint8_t client = CLIENT_SERIAL; // default client
// Perform some machine checks to make sure everything is good to go.
if (config->_checkLimitsAtInit && config->_axes->hasHardLimits()) {
if (limits_get_state()) {
sys.state = State::Alarm; // Ensure alarm state is active.
report_feedback_message(Message::CheckLimits);
}
}
// Check for and report alarm state after a reset, error, or an initial power up.
// NOTE: Sleep mode disables the stepper drivers and position can't be guaranteed.
// Re-initialize the sleep state as an ALARM mode to ensure user homes or acknowledges.
if (sys.state == State::ConfigAlarm) {
report_feedback_message(Message::ConfigAlarmLock);
} else if (sys.state == State::Alarm || sys.state == State::Sleep) {
report_feedback_message(Message::AlarmLock);
sys.state = State::Alarm; // Ensure alarm state is set.
} else {
// Check if the safety door is open.
sys.state = State::Idle;
if (config->_control->system_check_safety_door_ajar()) {
rtSafetyDoor = true;
protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state.
// Perform some machine checks to make sure everything is good to go.
if (config->_checkLimitsAtInit && config->_axes->hasHardLimits()) {
if (limits_get_state()) {
sys.state = State::Alarm; // Ensure alarm state is active.
report_feedback_message(Message::CheckLimits);
}
}
if (sys.state == State::Alarm || sys.state == State::Sleep) {
report_feedback_message(Message::AlarmLock);
sys.state = State::Alarm; // Ensure alarm state is set.
} else {
// Check if the safety door is open.
sys.state = State::Idle;
if (config->_control->system_check_safety_door_ajar()) {
rtSafetyDoor = true;
protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state.
}
// All systems go!
system_execute_startup(); // Execute startup script.
}
// All systems go!
system_execute_startup(); // Execute startup script.
}
// ---------------------------------------------------------------------------------
// Primary loop! Upon a system abort, this exits back to main() to reset the system.
// This is also where Grbl idles while waiting for something to do.

View File

@@ -295,7 +295,7 @@ std::map<Message, const char*> MessageText = {
{ Message::RestoreDefaults, "Restoring defaults" },
{ Message::SpindleRestore, "Restoring spindle" },
{ Message::SleepMode, "Sleeping" },
{ Message::ConfigAlarmLock, "Configuration error - '$X' to unlock" },
{ Message::ConfigAlarmLock, "Configuration is invalid. Check boot messages for ERR's." },
// Handled separately due to numeric argument
// { Message::SdFileQuit, "Reset during SD file at line: %d" },
};

View File

@@ -137,7 +137,7 @@ namespace Spindles {
dev_speed += (((speed - _speeds[i].speed) * _speeds[i].scale) >> 16);
}
log_debug("rpm " << speed << " speed " << dev_speed);
// log_debug("rpm " << speed << " speed " << dev_speed); // This will spew quite a bit of data on your output
return dev_speed;
}
void Spindle::spinDelay(SpindleState state, SpindleSpeed speed) {

View File

@@ -198,7 +198,7 @@ static void Stepper_Timer_Stop();
// Stepper timer configuration
const int stepTimerNumber = 0;
hw_timer_t* stepTimer; // Handle
hw_timer_t* stepTimer = nullptr; // Handle
// autoReload true might give better step timing - but it also
// might cause problems if an interrupt takes too long
const bool autoReload = true;
@@ -958,7 +958,7 @@ static void IRAM_ATTR Stepper_Timer_Start() {
static void IRAM_ATTR Stepper_Timer_Stop() {
if (config->_stepType == ST_I2S_STREAM) {
i2s_out_set_passthrough();
} else {
} else if (stepTimer) {
timerAlarmDisable(stepTimer);
}
}