diff --git a/Grbl_Esp32/src/Configuration/RuntimeSetting.cpp b/Grbl_Esp32/src/Configuration/RuntimeSetting.cpp index 0da2fcde..22591db1 100644 --- a/Grbl_Esp32/src/Configuration/RuntimeSetting.cpp +++ b/Grbl_Esp32/src/Configuration/RuntimeSetting.cpp @@ -18,22 +18,25 @@ #include "RuntimeSetting.h" +#include "../Report.h" + #include #include namespace Configuration { - RuntimeSetting::RuntimeSetting(const char* runtimeSetting) : setting_(runtimeSetting + 1), start_(runtimeSetting + 1) { + RuntimeSetting::RuntimeSetting(const char* key, const char* value, WebUI::ESPResponseStream* out) : + setting_(key), start_(key), newValue_(value), out_(out) { // Read fence for config. Shouldn't be necessary, but better safe than sorry. std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); } void RuntimeSetting::enterSection(const char* name, Configuration::Configurable* value) { - if (is(name) && this->value() == nullptr) { + if (is(name) && !isHandled_) { auto previous = start_; // Figure out next node auto next = start_; - for (; *next && *next != '=' && *next != '/'; ++next) {} + for (; *next && *next != '/'; ++next) {} // Do we have a child? if (*next == '/') { @@ -49,30 +52,108 @@ namespace Configuration { } } + void RuntimeSetting::item(const char* name, bool& value) { + if (is(name)) { + isHandled_ = true; + if (newValue_ == nullptr) { + grbl_sendf(out_->client(), "$%s=%s\r\n", setting_, value ? "true" : "false"); + } else { + value = (!strcmp(newValue_, "true")); + } + } + } + void RuntimeSetting::item(const char* name, int32_t& value, int32_t minValue, int32_t maxValue) { - if (is(name) && this->value() != nullptr) { - value = atoi(this->value()); + if (is(name)) { + isHandled_ = true; + if (newValue_ == nullptr) { + grbl_sendf(out_->client(), "$%s=%d\r\n", setting_, value); + } else { + value = atoi(newValue_); + } } } void RuntimeSetting::item(const char* name, float& value, float minValue, float maxValue) { - if (is(name) && this->value() != nullptr) { - char* floatEnd; - value = strtof(this->value(), &floatEnd); + if (is(name)) { + isHandled_ = true; + if (newValue_ == nullptr) { + grbl_sendf(out_->client(), "$%s=%.3f\r\n", setting_, value); + } else { + char* floatEnd; + value = strtof(newValue_, &floatEnd); + } } } void RuntimeSetting::item(const char* name, StringRange& value, int minLength, int maxLength) { - if (is(name) && this->value() != nullptr) { - value = this->value(); + if (is(name)) { + isHandled_ = true; + if (newValue_ == nullptr) { + grbl_sendf(out_->client(), "$%s=%s\r\n", setting_, value.str().c_str()); + } else { + value = StringRange(newValue_); + } + } + } + + void RuntimeSetting::item(const char* name, int& value, EnumItem* e) { + if (is(name)) { + isHandled_ = true; + if (newValue_ == nullptr) { + for (; e->name; ++e) { + if (e->value == value) { + grbl_sendf(out_->client(), "$%s=%s\r\n", setting_, e->name); + return; + } + } + } else { + for (; e->name; ++e) { + if (!strcmp(newValue_, e->name)) { + value = e->value; + return; + } + } + + if (strlen(newValue_) == 0) { + value = e->value; + return; + } else { + Assert(false, "Provided enum value %s is not valid", newValue_); + } + } + } + } + + void RuntimeSetting::item(const char* name, IPAddress& value) { + if (is(name)) { + isHandled_ = true; + if (newValue_ == nullptr) { + grbl_sendf(out_->client(), "$%s=%s\r\n", setting_, value.toString().c_str()); + } else { + IPAddress ip; + auto str = String(newValue_); + if (!ip.fromString(str)) { + Assert(false, "Expected an IP address like 192.168.0.100"); + } + value = ip; + } } } void RuntimeSetting::item(const char* name, Pin& value) { - if (is(name) && this->value() != nullptr) { - auto parsed = Pin::create(StringRange(this->value())); - value.swap(parsed); + /* + Runtime settings of PIN objects is NOT supported! + + if (is(name)) { + if (newValue_ == nullptr) { + grbl_sendf(out_->client(), "$%s=%s\r\n", setting_, value.name().c_str()); + } else { + auto parsed = Pin::create(StringRange(this->value())); + value.swap(parsed); + } } + */ } RuntimeSetting::~RuntimeSetting() { diff --git a/Grbl_Esp32/src/Configuration/RuntimeSetting.h b/Grbl_Esp32/src/Configuration/RuntimeSetting.h index 7bc4ce16..d3eac733 100644 --- a/Grbl_Esp32/src/Configuration/RuntimeSetting.h +++ b/Grbl_Esp32/src/Configuration/RuntimeSetting.h @@ -20,48 +20,46 @@ #include "HandlerBase.h" #include "Configurable.h" +#include "../WebUI/ESPResponse.h" namespace Configuration { class RuntimeSetting : public Configuration::HandlerBase { private: - const char* setting_; // $foo/bar=12 + const char* setting_; // foo/bar const char* start_; + const char* newValue_; // null (read) or 123 (value) + + WebUI::ESPResponseStream* out_; + bool is(const char* name) const { if (start_ != nullptr) { auto len = strlen(name); - return !strncmp(name, start_, len) && (start_[len] == '=' || start_[len] == '/'); + auto result = !strncasecmp(name, start_, len) && (start_[len] == '\0' || start_[len] == '/'); + return result; } else { return false; } } - const char* value() const { - for (const char* it = start_; *it; ++it) { - if (*it == '/') { - return nullptr; - } else if (*it == '=') { - return it + 1; - } - } - return nullptr; - } - protected: void enterSection(const char* name, Configuration::Configurable* value) override; - bool matchesUninitialized(const char* name) override { return false; } public: - RuntimeSetting(const char* runtimeSetting); + RuntimeSetting(const char* key, const char* value, WebUI::ESPResponseStream* out); + void item(const char* name, bool& value) override; void item(const char* name, int32_t& value, int32_t minValue, int32_t maxValue) override; void item(const char* name, float& value, float minValue, float maxValue) override; void item(const char* name, StringRange& value, int minLength, int maxLength) override; void item(const char* name, Pin& value) override; - void item(const char* name, int& value, EnumItem* e) override {} + void item(const char* name, IPAddress& value) override; + void item(const char* name, int& value, EnumItem* e) override; HandlerType handlerType() override { return HandlerType::Runtime; } + + bool isHandled_ = false; virtual ~RuntimeSetting(); }; diff --git a/Grbl_Esp32/src/Error.h b/Grbl_Esp32/src/Error.h index 733fd973..2c01cc07 100644 --- a/Grbl_Esp32/src/Error.h +++ b/Grbl_Esp32/src/Error.h @@ -86,6 +86,7 @@ enum class Error : uint8_t { AnotherInterfaceBusy = 120, JogCancelled = 130, BadPinSpecification = 150, + BadRuntimeConfigSetting = 151, }; extern std::map ErrorNames; diff --git a/Grbl_Esp32/src/ProcessSettings.cpp b/Grbl_Esp32/src/ProcessSettings.cpp index e63133bd..00247e27 100644 --- a/Grbl_Esp32/src/ProcessSettings.cpp +++ b/Grbl_Esp32/src/ProcessSettings.cpp @@ -3,6 +3,10 @@ #include "Regex.h" #include "MachineConfig.h" +#include "Configuration/RuntimeSetting.h" +#include "Configuration/AfterParse.h" +#include "Configuration/Validator.h" +#include "Configuration/ParseException.h" // WG Readable and writable as guest // WU Readable and writable as user and admin @@ -546,7 +550,37 @@ Error do_command_or_setting(const char* key, char* value, WebUI::AuthenticationL // $key= with nothing following the = . It is important to distinguish // those cases so that you can say "$N0=" to clear a startup line. - // First search the settings list by text name. If found, set a new + // First search the yaml settings by name. If found, set a new + // value if one is given, otherwise display the current value + try { + Configuration::RuntimeSetting rts(key, value, out); + config->group(rts); + + if (rts.isHandled_) { + try { + Configuration::Validator validator; + config->validate(); + config->group(validator); + } catch (std::exception& ex) { + log_error("Validation error: " << ex.what()); + return Error::BadRuntimeConfigSetting; + } + + Configuration::AfterParse afterParseHandler; + config->afterParse(); + config->group(afterParseHandler); + + return Error::Ok; + } + } catch (const Configuration::ParseException& ex) { + log_error("Configuration parse error: " << ex.What() << " @ " << ex.LineNumber() << ":" << ex.ColumnNumber()); + return Error::BadRuntimeConfigSetting; + } catch (const AssertionFailed& ex) { + log_error("Configuration change failed: " << ex.what()); + return Error::BadRuntimeConfigSetting; + } + + // Next search the settings list by text name. If found, set a new // value if one is given, otherwise display the current value for (Setting* s = Setting::List; s; s = s->next()) { if (strcasecmp(s->getName(), key) == 0) {