Skip to content

Instantly share code, notes, and snippets.

@triffid
Last active December 12, 2015 04:08
Show Gist options
  • Select an option

  • Save triffid/4712066 to your computer and use it in GitHub Desktop.

Select an option

Save triffid/4712066 to your computer and use it in GitHub Desktop.
proposed Smoothie config pattern
/** Intended design pattern: */
class ModulePool : public Module {
std::map<const char*,Module*> children; // list of name->Module pairs which are children of this ModulePool
};
class ConfigKey {
public:
ConfigKey(); // empty key, use for retrieving config settings
ConfigKey(const char* line); // extract key and value from the provided line
ConfigKey(const char* key, const char* value);
bool is_for_me(const char* name); // checks if (name) matches the current token
bool is_for_me(uint16_t name_checksum);
bool is_for_my_child(void); // returns true if there are more tokens in the key
bool pass_to_child(ModulePool*); // increments token, looks in std::map ModulePool::children[], passes and returns true if found, returns false if not found.
ConfigKey* by_default(const char*); // sets default and returns (this)
ConfigKey* by_default(int);
ConfigKey* by_default(double);
...
int as_int(void);
const char* as_char(void);
string as_string(void);
Pin* as_pin(void);
...
ConfigKey* next_token(void); // this moves pointers to the next token in the key and returns (this)
Hook* retrieve_hook; // when we retrieve, feed all config options to this Hook
ConfigKey* retrieve() { (*retrieve_hook)(this); } // call the above hook
ConfigKey* push_token(const char*); // add a token to the key
ConfigKey* pop_token(const char*); // remove the last token
ConfigKey* clean(); // remove all tokens, defaults and value
ConfigKey* provide_value(int); // provide a value for this key during retrieval
ConfigKey* provide_value(double);
ConfigKey* provide_value(const char*);
ConfigKey* provide_value(string);
ConfigKey* provide_value(Pwm*);
bool retrieve_all; // true to dump all settings to the hook, false if we're only after a specific one
protected:
const char* token_start;
const char* token_end;
const_char* value;
// value in other forms, defaults, etc
};
int main() {
k = new ConfigKey("temperature_control.hotend.heater_pin.max_pwm = 25%"); /** we're going to follow this particular call through in the comments */
kernel->call_event(ON_CONFIG_VALUE, k);
}
TemperatureControlPool::on_module_loaded() {
// this allows TemperatureControl to receive top-level configuration values
// our submodules should NOT register for on_config_value event, we will pass them suitably modified values.
register_for_event(ON_CONFIG_VALUE);
}
TemperatureControlPool::on_config_value(ConfigKey* k) {
if (k->is_for_me("temperature_control")) { // (true, first token is "temperature_control")
if (k->is_for_my_child()) { // (true, there is another token)
if (!k->pass_to_child(this)) { // (looks up "hotend" in std::map children[], invokes hotend->on_config_value(k->next_token()) on the result, or returns false if it is not found. Let's pretend we already added one.)
TemperatureControl* t = new TemperatureControl();
children[k->key()] = t;
}
}
else {
// someone has put (temperature_control = blah) in their config file
}
}
}
// Note: TemperatureControl, Pwm, Pin etc do NOT register for on_config_value
// only top-level modules such as Robot and TemperatureControlPool which are expected to be the first token in each config line should do that
// the top level modules will pass suitably altered config values into submodules' on_config_value event.
// this way, submodules don't need to know their own names. They simply don't receive any values that aren't intended for them.
TemperatureControl::on_config_value(ConfigKey* k) {
// called from TemperatureControPool::on_config_value via k->pass_to_child()
// current token is "heater_pin"
switch (k->checksum()) { // (checksum for current token "heater_pin")
case CHECKSUM("enable"):
break;
case CHECKSUM("thermistor_pin"):
if (k->is_for_my_child())
thermistor_pin->on_config_value(k);
else
thermistor_pin->from_string(k->as_string());
break;
case CHECKSUM("heater_pin"): // (true)
if (k->is_for_my_child()) // (true, next token is "max_pwm")
heater_pin->on_config_value(k->next_token()); // (now current token is "max_pwm" for the pass)
else
heater_pin->from_string(k->as_string());
break;
}
}
Pwm::on_config_value(ConfigKey* k) {
switch(k->checksum()) { // (checksum for "max_pwm")
case CHECKSUM("max_pwm"): // (true)
_max_pwm = k->by_default(255)->as_int(); // allows raw number, or percentage. 25% -> (255 * 25 / 100) = 63
break;
}
}
/*
* this is the proposal for retrieving config values, including a full enumeration of all values
*/
static FILE FileConfigSource::f;
static char* FileConfigSource::buffer;
FileConfigSource::on_config_retrieve(ConfigKey* k)
{
char* b = buffer;
int s = snprintf(b, 128, "%s = %s\n", k->key(), k->as_char());
if (s > 127) {
b = malloc(s + 1);
snprintf(b, s, "%s = %s\n", k->key(), k->as_char());
}
fwrite(&f, b, s);
if (b != buffer)
free(b);
}
FileConfigSource::on_gcode_received(Gcode* g)
{
ConfigKey k;
Hook h;
h->attach(this, FileConfigSource::on_config_retrieve);
k.retrieve_hook = &h;
if (g->has_m && g->m = 503) { // M503 save config
buffer = malloc(128);
fopen(&f, saved_config);
kernel->call_event(ON_CONFIG_RETRIEVE, &k);
fclose(&f);
}
}
TemperatureControlPool::on_module_loaded() {
register_for_event(ON_CONFIG_RETRIEVE); // we're a top level module and we want the retrieve event
}
TemperatureControlPool::on_config_retrieve(ConfigKey* k) {
if (k->retrieve_all) {
for (map<const char*,ModulePool*>::iterator i = children.begin(); i != chilren.end(); i++) {
k->clean(); // only top-level modules can clean, submodules must pop their tokens instead
k->push_token("temperature_control"); // first token is temperature_control
k->push_token((*i)->first); // next token is temperature controller name
}
(*i)->second->on_config_retrieve(k); // controller can fill in the rest from here
k->clean(); // return with a clean configkey
}
else if (k->is_for_me("temperature_control")) { // if we're retrieving something that starts with "temperature_control"
k->next_token(); // move token to the name of the TemperatureControl we're after
for (map<const char*,ModulePool*>::iterator i = children.begin(); i != chilren.end(); i++) {
if (strncasecmp((*i)->first, k->token(), k->tokenlength()) == 0) { // if this is the referenced temperature control,
(*i)->second->on_config_retrieve(k->next_token()); // advance token again and pass to the found TemperatureControl
}
}
}
}
TemperatureControl::on_config_retrieve(ConfigKey* k) {
if (k->retrieve_all) {
// k->key now contains "temperature_control.hotend" or similar
// we now push one of our settings onto the end, provide a value then call the retrieve hook
// once that's done, we pop the setting token and do the next one
k->push_token("enabled")->provide_value(true)->retrieve()->pop_token(1);
// we don't pop immediately after the retrieve here,
k->push_token("thermistor_pin")->provide_value(thermistor_pin)->retrieve();
// because here we pass "temperature_control.hotend.thermistor_pin" into the Pin's on_config_retrieve in case it wants to add something
thermistor_pin->on_config_retrieve(k);
// once that's done, we pop "thermistor_pin" ready for the next setting
k->pop_token(1);
k->push_token("heater_pin")->provide_value(heater_pin)->retrieve();
heater_pin->on_config_retrieve(k);
k->pop_token(1);
k->push_token("thermistor" )->provide_value(thermistor )->retrieve()->pop_token(1);
k->push_token("set_m_code" )->provide_value(set_m_code )->retrieve()->pop_token(1);
k->push_token("wait_m_code")->provide_value(wait_m_code)->retrieve()->pop_token(1);
k->push_token("designator" )->provide_value(designator )->retrieve()->pop_token(1);
}
else {
// k contains something like "temperature_control.hotend.thermistor_pin.max_pwm" with key() pointing to "thermistor_pin"
switch (k->checksum()) {
case CHECKSUM("enabled"):
k->provide_value(enabled)->retrieve();
break;
case CHECKSUM("thermistor_pin"): // (true), we're after the thermistor pin
if (k->is_for_my_child()) // if we're after thermistor_pin.something, pass the "something" to the Pin
thermistor_pin->on_config_retrieve(k->next_token());
else // otherwise we stringify the pin itself and invoke the retrieve hook with our discovered setting
k->provide_value(thermistor_pin)->retrieve();
// etc
}
}
}
Pin::on_config_retrieve(ConfigKey* k) {
if (k->retrieve_all) {
if (!output) // input
// perhaps a mode for input pins, handy with endstops etc
k->push_token("mode")->provide_value(("pull-up","pull-down","open-drain","high-z")[pinmode])->retrieve()->pop_token();
}
else {
// k contains something like "temperature_control.hotend.thermistor_pin.max_pwm" with key() pointing to "max_pwm"
if (k->checksum() == CHECKSUM("mode"))
k->provide_value(("pull-up","pull-down","open-drain","high-z")[pinmode])->retrieve();
}
}
Pwm::on_config_retrieve(ConfigKey* k) {
Pin::on_config_retrieve(k);
if (k->retrieve_all) {
if (output)
// this is our only user editable setting if we're an output
k->push_token("max_pwm")->provide_value(_max_pwm)->retrieve()->pop_token();
else // input
// perhaps a mode for input pins, handy with endstops etc
k->push_token("mode")->provide_value(("pull-up","pull-down","open-drain","high-z")[pinmode])->retrieve()->pop_token();
}
else {
// k contains something like "temperature_control.hotend.thermistor_pin.max_pwm" with key() pointing to "max_pwm"
if (k->checksum() == CHECKSUM("max_pwm"))
k->provide_value(_max_pwm)->retrieve();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment