Last active
December 12, 2015 04:08
-
-
Save triffid/4712066 to your computer and use it in GitHub Desktop.
proposed Smoothie config pattern
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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