1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-09-02 02:42:36 +02:00

Implemented more configuration code.

This commit is contained in:
Stefan de Bruijn
2021-01-01 22:58:48 +01:00
parent 2f4ef0220e
commit 7418bb73eb
11 changed files with 433 additions and 576 deletions

View File

@@ -0,0 +1,18 @@
#pragma once
#include "Generator.h"
#include "Parser.h"
namespace Configuration
{
class Configurable
{
Configurable(const Configurable&) = delete;
Configurable& operator=(const Configurable&) = delete;
public:
Configurable() = default;
virtual void generate(Generator& generator) const = 0;
};
}

View File

@@ -0,0 +1,72 @@
#include "Generator.h"
#include "Configurable.h"
#include <cstring>
namespace Configuration
{
void Generator::enter(const char* name)
{
indent();
addStr(name);
addStr(":\n");
indent_++;
}
void Generator::add(const char* key, const std::string& value)
{
add(key, value.c_str());
}
void Generator::add(const char* key, const char* value)
{
indent();
addStr(key);
addStr(": ");
addStr(value);
addStr("\n");
}
void Generator::add(const char* key, bool value)
{
if (value) { add(key, "true"); }
else { add(key, "false"); }
}
void Generator::add(const char* key, int value)
{
char tmp[11];
snprintf(tmp, 11, "%d", value);
add(key, tmp);
}
void Generator::add(const char* key, double value)
{
char tmp[20];
snprintf(tmp, 20, "%f", value);
add(key, tmp);
}
void Generator::add(const char* key, Pin value)
{
if (!value.undefined())
{
add(key, value.str());
}
}
void Generator::add(Configuration::Configurable* configurable)
{
if (configurable != nullptr)
{
configurable->generate(*this);
}
}
void Generator::leave()
{
addStr("\n");
indent_--;
}
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <string>
#include <vector>
#include "../Pin.h"
namespace Configuration
{
class Configurable;
class Generator
{
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
std::vector<char> config_;
int indent_;
inline void addStr(const char* text) {
for (auto it = text; *it; ++it)
{
config_.push_back(*it);
}
}
inline void indent() {
for (int i = 0; i < indent_ * 2; ++i)
{
config_.push_back(' ');
}
}
public:
Generator() = default;
void enter(const char* name);
void add(const char* key, const std::string& value);
void add(const char* key, const char* value);
void add(const char* key, bool value);
void add(const char* key, int value);
void add(const char* key, double value);
void add(const char* key, Pin value);
void add(Configuration::Configurable* configurable);
void leave();
inline std::string str() const {
return std::string(config_.begin(), config_.end());
}
};
}

View File

@@ -0,0 +1,75 @@
#pragma once
#include <string>
#include <vector>
namespace Configuration {
template <typename BaseType>
class GenericFactory
{
static GenericFactory& instance() {
static GenericFactory instance_;
return instance_;
}
GenericFactory() = default;
GenericFactory(const GenericFactory&) = delete;
GenericFactory& operator=(const GenericFactory&) = delete;
class BuilderBase {
const char* name_;
public:
BuilderBase(const char* name) : name_(name) {}
BuilderBase(const BuilderBase& o) = delete;
BuilderBase& operator=(const BuilderBase& o) = delete;
virtual BaseType* create(Configuration::Parser& parser) const = 0;
inline bool matches(const char* name) {
return !strcmp(name, name_);
}
virtual ~BuilderBase() = default;
};
std::vector<BuilderBase*> builders_;
inline static void registerBuilder(BuilderBase* builder)
{
instance().builders_.push_back(builder);
}
public:
template <typename DerivedType>
class InstanceBuilder : public BuilderBase
{
public:
InstanceBuilder(const char* name) : BuilderBase(name) {
instance().registerBuilder(this);
}
BaseType* create(Configuration::Parser& parser) const override
{
return new DerivedType(parser);
}
};
static const BuilderBase* find(const char* name) {
for (auto it : instance().builders_)
{
if (it->matches(name))
{
return it;
}
}
return nullptr;
}
inline static const BuilderBase* find(const std::string& name) {
return find(name.c_str());
}
};
}

View File

@@ -11,7 +11,7 @@ namespace Configuration {
}
}
void Parser::ParseError(const char* description) const {
void Parser::parseError(const char* description) const {
// Attempt to use the correct position in the parser:
if (current_.keyEnd_) {
throw ParseException(start_, current_.keyEnd_, description);
@@ -25,7 +25,7 @@ namespace Configuration {
/// MoveNext: moves to the next entry in the current section. By default we're in the
/// root section.
/// </summary>
bool Parser::MoveNext() {
bool Parser::moveNext() {
// While the indent of the token is > current indent, we have to skip it. This is a
// sub-section, that we're apparently not interested in.
while (token_.indent_ > current_.indent_) {
@@ -48,7 +48,7 @@ namespace Configuration {
return current_.kind_ != TokenKind::Eof;
}
void Parser::Enter() {
void Parser::enter() {
indentStack_.push(current_.indent_);
// If we can enter, token_.indent_ > current_.indent_:
@@ -59,9 +59,10 @@ namespace Configuration {
current_ = TokenData();
current_.indent_ = INT_MAX;
}
indent_ = current_.indent_;
}
void Parser::Leave() {
void Parser::leave() {
// While the indent of the tokenizer is >= current, we can ignore the contents:
while (token_.indent_ >= current_.indent_ && token_.kind_ != TokenKind::Eof) {
Tokenize();
@@ -70,6 +71,7 @@ namespace Configuration {
// At this point, we just know the indent is smaller. We don't know if we're in
// the *right* section tho.
auto last = indentStack_.top();
indent_ = last;
indentStack_.pop();
if (last == token_.indent_) {
@@ -82,31 +84,40 @@ namespace Configuration {
}
}
std::string Parser::StringValue() const {
std::string Parser::stringValue() const {
if (current_.kind_ != TokenKind::String) {
ParseError("Expected a string value (e.g. 'foo')");
parseError("Expected a string value (e.g. 'foo')");
}
return std::string(current_.sValueStart_, current_.sValueEnd_);
}
bool Parser::BoolValue() const {
bool Parser::boolValue() const {
if (current_.kind_ != TokenKind::Boolean) {
ParseError("Expected a boolean value (e.g. true or value)");
parseError("Expected a boolean value (e.g. true or value)");
}
return current_.bValue_;
}
int Parser::IntValue() const {
int Parser::intValue() const {
if (current_.kind_ != TokenKind::Boolean) {
ParseError("Expected an integer value (e.g. 123456)");
parseError("Expected an integer value (e.g. 123456)");
}
return current_.iValue_;
}
double Parser::FloatValue() const {
double Parser::floatValue() const {
if (current_.kind_ != TokenKind::Boolean) {
ParseError("Expected a float value (e.g. 123.456)");
parseError("Expected a float value (e.g. 123.456)");
}
return current_.fValue_;
}
Pin Parser::pinValue() const
{
if (current_.kind_ != TokenKind::String) {
parseError("Expected a string value (e.g. 'foo')");
}
return Pin(std::string(current_.sValueStart_, current_.sValueEnd_));
}
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Tokenizer.h"
#include "../Pin.h"
#include <stack>
#include <cstring>
@@ -13,8 +14,9 @@ namespace Configuration {
std::stack<int> indentStack_;
TokenData current_;
int indent_ = 0;
void ParseError(const char* description) const;
void parseError(const char* description) const;
public:
Parser(const char* start, const char* end);
@@ -23,27 +25,28 @@ namespace Configuration {
/// MoveNext: moves to the next entry in the current section. By default we're in the
/// root section.
/// </summary>
bool MoveNext();
bool moveNext();
inline bool IsEndSection() { return current_.kind_ == TokenKind::Eof || token_.indent_ < current_.indent_; }
inline bool isEndSection() { return current_.kind_ == TokenKind::Eof || current_.indent_ < indent_; }
// !!! Important !!! We cannot use a scoped variable for enter & leave, because 'leave' can throw,
// and it could be called using stack unrolling. Destructors by definition have to be 'nothrow',
// so forget it: it just Won't Work. In other words, if we leave the 'leave' call up to the
// destructor, we end up what we in C++ call 'undefined behavior'.
void Enter();
void Leave();
void enter();
void leave();
inline bool Is(const char* expected) const {
inline bool is(const char* expected) const {
return !strncmp(expected, current_.keyStart_, size_t(current_.keyEnd_ - current_.keyStart_));
}
inline std::string Key() const { return std::string(current_.keyStart_, current_.keyEnd_); }
inline std::string key() const { return std::string(current_.keyStart_, current_.keyEnd_); }
std::string StringValue() const;
bool BoolValue() const;
int IntValue() const;
double FloatValue() const;
std::string stringValue() const;
bool boolValue() const;
int intValue() const;
double floatValue() const;
Pin pinValue() const;
};
}

View File

@@ -1,93 +0,0 @@
#ifndef ESP32
# include "Parser.h"
# include <string>
# include <fstream>
# include <iostream>
# include <streambuf>
namespace Configuration {
void ParseSpecificAxis(Parser& parser, int axis, int ganged) {
const char* allAxis = "xyzabc";
std::cout << "Parsing axis " << allAxis[axis] << ", ganged #" << ganged << std::endl;
for (; !parser.IsEndSection(); parser.MoveNext()) {
if (parser.Is("limit")) {
auto limitPin = parser.StringValue();
std::cout << "Limit pin: " << limitPin << std::endl;
}
// and so on...
}
}
void ParseAxis(Parser& parser) {
std::cout << "Parsing axis." << std::endl;
const char* allAxis = "xyzabc";
for (; !parser.IsEndSection(); parser.MoveNext()) {
auto str = parser.Key();
if (str.size() == 1) {
auto idx = strchr(allAxis, str[0]);
if (idx != nullptr) {
parser.Enter();
ParseSpecificAxis(parser, idx - allAxis, 0);
parser.Leave();
}
} else if (str.size() == 2) {
auto idx = strchr(allAxis, str[0]);
if (idx != nullptr && str[1] >= '1' && str[1] <= '9') {
int ganged = str[1] - '1';
parser.Enter();
ParseSpecificAxis(parser, idx - allAxis, ganged);
parser.Leave();
}
}
}
}
void ParseBus(Parser& parser) {
std::cout << "Parsing bus." << std::endl;
// TODO
}
void ParseRoot(Parser& parser) {
std::cout << "Parsing root." << std::endl;
for (; !parser.IsEndSection(); parser.MoveNext()) {
if (parser.Is("axis")) {
parser.Enter();
ParseAxis(parser);
parser.Leave();
} else if (parser.Is("bus")) {
parser.Enter();
ParseBus(parser);
parser.Leave();
}
}
}
int main() {
std::ifstream t("..\\Fiddling\\Test.yaml");
std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
const auto begin = str.c_str();
const auto end = begin + str.size();
try {
Parser parser(begin, end);
ParseRoot(parser);
} catch (ParseException ex) {
std::cout << "Parse error: " << ex.What() << " @ " << ex.LineNumber() << ":" << ex.ColumnNumber() << std::endl;
} catch (...) { std::cout << "Uncaught exception" << std::endl; }
std::string s;
std::getline(std::cin, s);
return 0;
}
}
#endif

View File

@@ -0,0 +1 @@
#pragma once

View File

@@ -1,28 +0,0 @@
axis:
x:
limit: gpio.33:low
stepstick:
step: gpio.4
direction: gpio.16
y2:
limit: gpio.32:low
stepstick:
step: gpio.18
direction: gpio.18
z:
limit: gpio.34:low
dynamixel:
channel: 3
#yeah this rocks
bus:
rs485:
baud: 19200
rx: gpio.12
tx: gpio.13
spindle:
vfd:
channel: 1
unattached: true
vfd:
channel: 2

View File

@@ -4,177 +4,8 @@
#include <cstdlib>
namespace Configuration {
inline void Tokenizer::ParseError(const char* description) const { throw ParseException(start_, current_, description); }
void Tokenizer::Tokenize() {
// We parse 1 line at a time. Each time we get here, we can assume that the cursor
// is at the start of the line.
parseAgain:
int indent = 0;
while (!Eof() && IsSpace()) {
Inc();
++indent;
}
if (!Eof()) {
switch (Current()) {
case '\t':
// TODO FIXME: We can do tabs or spaces, not both. However, we *could* let the user decide.
ParseError("Indentation through tabs is not allowed. Convert all tabs to spaces please.");
break;
case '#': // Comment till end of line
Inc();
while (!Eof() && !IsEndLine()) {
Inc();
}
return;
case '\r':
case '\n':
Inc();
if (!Eof() && Current() == '\n') {
Inc();
} // \r\n
goto parseAgain;
default:
if (!IsAlpha()) {
ParseError("Expected identifier.");
}
token_.keyStart_ = current_;
Inc();
while (!Eof() && (IsAlpha() || IsDigit() || Current() == '_')) {
Inc();
}
token_.keyEnd_ = current_;
// Skip whitespaces:
while (!Eof() && IsWhiteSpace()) {
Inc();
}
if (Current() != ':') {
ParseError("After a key or section name, we expect a colon character ':'.");
}
Inc();
// Skip whitespaces after the colon:
while (!Eof() && IsWhiteSpace()) {
Inc();
}
token_.indent_ = indent;
if (IsEndLine()) {
token_.kind_ = TokenKind::Section;
Inc();
if (!Eof() && Current() == '\n') {
Inc();
} // \r\n
} else {
switch (Current()) {
case '"':
case '\'': {
auto delimiter = Current();
token_.kind_ = TokenKind::String;
Inc();
token_.sValueStart_ = current_;
while (!Eof() && Current() != delimiter && !IsEndLine()) {
Inc();
}
token_.sValueEnd_ = current_;
if (Current() != delimiter) {
ParseError("Could not find matching delimiter in string value.");
}
Inc();
} break;
default:
if (EqualsCaseInsensitive("true")) {
token_.kind_ = TokenKind::Boolean;
token_.bValue_ = true;
for (auto i = 0; i < 4; ++i) {
Inc();
}
} else if (EqualsCaseInsensitive("false")) {
token_.kind_ = TokenKind::Boolean;
token_.bValue_ = false;
for (auto i = 0; i < 5; ++i) {
Inc();
}
} else if (IsDigit() || Current() == '-') {
auto doubleOrIntStart = current_;
int intValue = 0;
bool negative = false;
if (Current() == '-') {
Inc();
negative = true;
}
while (IsDigit()) {
intValue = intValue * 10 + int(Current() - '0');
Inc();
}
if (Current() == 'e' || Current() == 'E' || Current() == '.' || // markers
(current_ - doubleOrIntStart) >= 9) { // liberal interpretation of 'out of int range'
char* floatEnd;
token_.fValue_ = strtod(doubleOrIntStart, &floatEnd);
token_.kind_ = TokenKind::FloatingPoint;
current_ = floatEnd;
} else {
if (negative) {
intValue = -intValue;
}
token_.iValue_ = intValue;
token_.kind_ = TokenKind::IntegerValue;
}
} else {
// If it's not 'true', not 'false', and not a digit, we have a string delimited by a whitespace
token_.kind_ = TokenKind::String;
token_.sValueStart_ = current_;
while (!Eof() && !IsWhiteSpace() && !IsEndLine()) {
Inc();
}
token_.sValueEnd_ = current_;
}
break;
}
// Skip more whitespaces
while (!Eof() && IsSpace()) {
Inc();
}
// A comment after a key-value pair is allowed.
if (Current() == '#') {
Inc();
while (!Eof() && !IsEndLine()) {
Inc();
}
}
// Should be EOL or EOF at this point.
if (!IsEndLine() && !Eof()) {
ParseError("Expected line end after key/value pair.");
}
}
}
} else {
token_.kind_ = TokenKind::Eof;
token_.indent_ = 0;
}
}
inline Tokenizer::Tokenizer(const char* start, const char* end) : start_(start), current_(start), end_(end), token_() {
Tokenizer::Tokenizer(const char* start, const char* end) : start_(start), current_(start), end_(end), token_() {
// If start is a yaml document start ('---' [newline]), skip that first.
if (EqualsCaseInsensitive("---")) {
for (int i = 0; i < 3; ++i) {
@@ -190,4 +21,182 @@ namespace Configuration {
start_ = current_;
}
}
void Tokenizer::ParseError(const char* description) const { throw ParseException(start_, current_, description); }
void Tokenizer::Tokenize() {
// We parse 1 line at a time. Each time we get here, we can assume that the cursor
// is at the start of the line.
parseAgain:
int indent = 0;
while (!Eof() && IsSpace()) {
Inc();
++indent;
}
if (!Eof()) {
switch (Current()) {
case '\t':
// TODO FIXME: We can do tabs or spaces, not both. However, we *could* let the user decide.
ParseError("Indentation through tabs is not allowed. Convert all tabs to spaces please.");
break;
case '#': // Comment till end of line
Inc();
while (!Eof() && !IsEndLine()) {
Inc();
}
return;
case '\r':
case '\n':
Inc();
if (!Eof() && Current() == '\n') {
Inc();
} // \r\n
goto parseAgain;
default:
if (!IsAlpha()) {
ParseError("Expected identifier.");
}
token_.keyStart_ = current_;
Inc();
while (!Eof() && (IsAlpha() || IsDigit() || Current() == '_')) {
Inc();
}
token_.keyEnd_ = current_;
// Skip whitespaces:
while (!Eof() && IsWhiteSpace()) {
Inc();
}
if (Current() != ':') {
ParseError("After a key or section name, we expect a colon character ':'.");
}
Inc();
// Skip whitespaces after the colon:
while (!Eof() && IsWhiteSpace()) {
Inc();
}
token_.indent_ = indent;
if (IsEndLine()) {
token_.kind_ = TokenKind::Section;
Inc();
if (!Eof() && Current() == '\n') {
Inc();
} // \r\n
}
else {
switch (Current()) {
case '"':
case '\'': {
auto delimiter = Current();
token_.kind_ = TokenKind::String;
Inc();
token_.sValueStart_ = current_;
while (!Eof() && Current() != delimiter && !IsEndLine()) {
Inc();
}
token_.sValueEnd_ = current_;
if (Current() != delimiter) {
ParseError("Could not find matching delimiter in string value.");
}
Inc();
} break;
default:
if (EqualsCaseInsensitive("true")) {
token_.kind_ = TokenKind::Boolean;
token_.bValue_ = true;
for (auto i = 0; i < 4; ++i) {
Inc();
}
}
else if (EqualsCaseInsensitive("false")) {
token_.kind_ = TokenKind::Boolean;
token_.bValue_ = false;
for (auto i = 0; i < 5; ++i) {
Inc();
}
}
else if (IsDigit() || Current() == '-') {
auto doubleOrIntStart = current_;
int intValue = 0;
bool negative = false;
if (Current() == '-') {
Inc();
negative = true;
}
while (IsDigit()) {
intValue = intValue * 10 + int(Current() - '0');
Inc();
}
if (Current() == 'e' || Current() == 'E' || Current() == '.' || // markers
(current_ - doubleOrIntStart) >= 9) { // liberal interpretation of 'out of int range'
char* floatEnd;
token_.fValue_ = strtod(doubleOrIntStart, &floatEnd);
token_.kind_ = TokenKind::FloatingPoint;
current_ = floatEnd;
}
else {
if (negative) {
intValue = -intValue;
}
token_.iValue_ = intValue;
token_.kind_ = TokenKind::IntegerValue;
}
}
else {
// If it's not 'true', not 'false', and not a digit, we have a string delimited by a whitespace
token_.kind_ = TokenKind::String;
token_.sValueStart_ = current_;
while (!Eof() && !IsWhiteSpace() && !IsEndLine()) {
Inc();
}
token_.sValueEnd_ = current_;
}
break;
}
// Skip more whitespaces
while (!Eof() && IsSpace()) {
Inc();
}
// A comment after a key-value pair is allowed.
if (Current() == '#') {
Inc();
while (!Eof() && !IsEndLine()) {
Inc();
}
}
// Should be EOL or EOF at this point.
if (!IsEndLine() && !Eof()) {
ParseError("Expected line end after key/value pair.");
}
}
}
}
else {
token_.kind_ = TokenKind::Eof;
token_.indent_ = 0;
}
}
}

View File

@@ -23,267 +23,10 @@
*/
#include "Spindle.h"
#include <cstring>
#include <vector>
#include <tuple>
namespace Settings {
class YamlParser {
// I didn't create a full parser, as we don't need it.
// See: https://en.wikipedia.org/wiki/YAML
//
// Supported:
// - We start with '---' and assume a single document. If we encounter '---', error.
// - # is a comment
// - indentation is with spaces
// - Tabs are not allowed for indentation
// - key: value pairs
// - strings with quotes
//
// Unsupported:
// - '-' lists
// - c-style escaping in strings
// - repeated nodes are initially denoted by an ampersand (&) and thereafter referenced with an asterisk (*).
// - '['..']', !! and % are simply not supported.
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<SettingLeaf*> 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<Item> 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 <typename Category>
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::Spindle*> spindles_;
struct MySettings : Settings::Setting<SpindleCollection> {
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<Spindles::Spindle*>(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<Machine> {
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<SpindleCollection*>(builder->parse(parser));
}
// else more sections...
}
// Use copy constructor here. It costs some time, but makes RAII much easier.
return new Machine(machine);
}
};
~Machine() { delete spindles_; }
};
namespace Spindles {
// This adds support for PWM
class PWM : public Spindle {
public:
struct MySettings : Settings::Setting<Spindle> {
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() = default;
PWM(Pin output, Pin enable, Pin direction, uint32_t minRpm, uint32_t maxRpm) :
@@ -330,8 +73,3 @@ namespace Spindles {
uint8_t calc_pwm_precision(uint32_t freq);
};
}
// Register settings class works like this:
namespace {
Spindles::PWM::MySettings pwmSettings;
}