diff --git a/Grbl_Esp32/src/PinUsers/PwmPin.cpp b/Grbl_Esp32/src/PinUsers/PwmPin.cpp index f801d978..81831f9c 100644 --- a/Grbl_Esp32/src/PinUsers/PwmPin.cpp +++ b/Grbl_Esp32/src/PinUsers/PwmPin.cpp @@ -81,7 +81,7 @@ namespace PinUsers { } public: - NativePwm(Pin pin, uint32_t frequency, uint32_t maxDuty) : frequency_(frequency), maxDuty_(maxDuty), pin_(pin) { + NativePwm(Pin pin, uint32_t frequency, uint32_t maxDuty) : pin_(pin), frequency_(frequency), maxDuty_(maxDuty){ auto native = pin.getNative(Pin::Capabilities::PWM | Pin::Capabilities::Native); pwmChannel_ = TryGrabChannel(frequency); diff --git a/Grbl_Esp32/src/Spindles/PWMSpindle.h b/Grbl_Esp32/src/Spindles/PWMSpindle.h index 994f790a..99abd376 100644 --- a/Grbl_Esp32/src/Spindles/PWMSpindle.h +++ b/Grbl_Esp32/src/Spindles/PWMSpindle.h @@ -23,11 +23,253 @@ */ #include "Spindle.h" +#include +#include +#include + +namespace Settings { + class YamlParser { + int n = 0; + + public: + // TODO FIXME: Create a parser. This is just a test. + // + // We have to think this through a bit. We want to stream the key/values + // in some way, but at the same time make it easy to use. Perhaps a + // simple 'movenext', 'token type', 'key' and 'value' will do. + + bool moveNext() { return n++ < 1; } + const char* key() { return "spindles"; } + const char* value() { return "gpio.12"; } + /* + const char* getValue(const char* key) { + if (!strcmp(key, "output")) { + return "gpio.12"; + } else if (!strcmp(key, "direction")) { + return "gpio.13"; + } else if (!strcmp(key, "enable")) { + return "gpio.14"; + } + return nullptr; + } + + const char* getNextSection() + { + if (n == 0) { + return "spindles"; + } + else { + return "pwm"; + } + } + */ + }; + + // Everything that uses the parser derives from this. + class SettingsParser { + public: + virtual void* parse(YamlParser& parser) = 0; + }; + + class SettingLeaf; + + class SettingsNode : public SettingsParser { + protected: + std::vector myLeafs; + + public: + static SettingsNode*& CurrentContainer() { + static SettingsNode* current = nullptr; + return current; + } + + SettingsNode() { CurrentContainer() = this; } + + void Add(SettingLeaf* leaf) { myLeafs.push_back(leaf); } + }; + + class SettingsCollection { + struct Item { + const char* name; + const char* parent; + SettingsNode* builder; + }; + + SettingsCollection() = default; + + static SettingsCollection& instance() { + static SettingsCollection instance; + return instance; + } + + std::vector builders_; + + public: + static void registerSetting(const char* name, const char* parent, SettingsNode* builder) { + instance().builders_.push_back({ name, parent, builder }); + } + + static SettingsNode* find(const char* name, const char* parent) { + if (parent != nullptr) { + for (auto& it : instance().builders_) { + if (!strcmp(it.name, name) && !strcmp(it.parent, parent)) { + return it.builder; + } + } + } else { + for (auto& it : instance().builders_) { + if (!strcmp(it.name, name) && it.parent == nullptr) { + return it.builder; + } + } + } + return nullptr; + } + }; + + // Leafs: basically key's with some value. + class SettingLeaf { + protected: + const char* key_; + + public: + SettingLeaf(const char* key) : key_(key) { + auto currentContainer = SettingsNode::CurrentContainer(); + if (currentContainer != nullptr) { + currentContainer->Add(this); + } + } + + const char* key() const { return key_; } + virtual void parse(YamlParser& parser) = 0; + }; + + class PinSetting : SettingLeaf { + Pin value_; + + public: + PinSetting(const char* key) : SettingLeaf(key) {} + + void parse(YamlParser& parser) override { value_ = Pin::create(parser.value()); } + Pin value() const { return value_; } + }; + + // When an error occurs, we want details. Let's just throw an error with these details, and have the + // framework clean up the mess. + class SettingsError { + SettingsError() = default; + + public: + SettingsError(const char* message, ...) { + // vsnprintf etc. + } + }; + + // NOTE: Settings just _define_ settings, and are very temporary objects. The lifetime of the members in a + // Settings object is basically the lifetime at which that section is parsed. This is very important, because + // if you use member variables for -say- motors, they can bleed through in other axis. In other words: it's + // best not to use member variables in settings expect for leaf settings. + template + class Setting : SettingsNode { + public: + Setting(const char* settingName, const char* parentName) { SettingsCollection::registerSetting(settingName, parentName, this); } + + virtual Category* create() = 0; + + virtual void* parse(YamlParser& parser) { + for (auto leaf : myLeafs) { + leaf->parse(parser); + } + return create(); + } + + virtual ~Setting() {} + }; +} + +class SpindleCollection { +public: + std::vector spindles; + + struct MySettings : Settings::Setting { + const char* collectionName = "spindles"; + + void* parse(Settings::YamlParser& parser) override { + SpindleCollection collection; + + while (parser.moveNext()) { + auto builder = Settings::SettingsCollection::find(parser.key(), collectionName); + Assert(builder != nullptr, "Settings invalid; incorrect key found: %s", parser.key()); + + // Unfortunately we cannot use dynamic_cast here, because we lack RTTI. + auto spindle = static_cast(builder->parse(parser)); + collection.spindles.push_back(spindle); + } + + // Use copy constructor here. It costs some time, but makes RAII much easier. + return new SpindleCollection(collection); + } + }; + + ~SpindleCollection() { + for (auto spindle : spindles) { + delete spindle; + } + } +}; + +class Machine { +public: + SpindleCollection* spindles = nullptr; + // Axis, misc devices, etc. + + struct MySettings : Settings::Setting { + MySettings() : Setting("machine", nullptr) {} + + void* parse(Settings::YamlParser& parser) override { + Machine machine; + + while (parser.moveNext()) { + auto builder = Settings::SettingsCollection::find(parser.key(), nullptr); + Assert(builder != nullptr, "Settings invalid; incorrect key found: %s", parser.key()); + + // Unfortunately we cannot use dynamic_cast here, because we lack RTTI. + if (!strcmp(parser.key(), "spindles")) { + Assert(machine.spindles == nullptr, "No spindles should be defined at this point. Only one spindle section is allowed."); + machine.spindles = static_cast(builder->parse(parser)); + } + // else more sections... + } + + // Use copy constructor here. It costs some time, but makes RAII much easier. + return new Machine(machine); + } + }; +}; + namespace Spindles { // This adds support for PWM class PWM : public Spindle { public: - PWM() = default; + struct MySettings : Settings::Setting { + MySettings() : Setting("pwm", "spindles") {} // note we can make 'spindles' a const char* in the Spindle class + + Settings::PinSetting outputPin = "output"; + Settings::PinSetting directionPin = "direction"; + Settings::PinSetting enablePin = "enable"; + + Spindle* create() override { + if (outputPin.value() == Pin::UNDEFINED) { + throw Settings::SettingsError("Output pin is undefined."); + } + // etc. + + return new PWM(outputPin.value(), enablePin.value(), directionPin.value(), 0, 10000); + } + }; + + PWM(Pin output, Pin enable, Pin direction, uint32_t minRpm, uint32_t maxRpm) : + _output_pin(output), _enable_pin(enable), _direction_pin(direction), _min_rpm(minRpm), _max_rpm(maxRpm) {} PWM(const PWM&) = delete; PWM(PWM&&) = delete; @@ -70,3 +312,7 @@ namespace Spindles { uint8_t calc_pwm_precision(uint32_t freq); }; } + +namespace { + Spindles::PWM::Settings pwmSettings; +}