/* * config.cpp - application independent configuration handling * This file is part of the g2core project * * Copyright (c) 2010 - 2019 Alden S. Hart, Jr. * * This file ("the software") is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2 as published by the * Free Software Foundation. You should have received a copy of the GNU General Public * License, version 2 along with the software. If not, see . * * As a special exception, you may use this file as part of a software library without * restriction. Specifically, if other files instantiate templates or use macros or * inline functions from this file, or you compile this file and link it with other * files to produce an executable, this file does not by itself cause the resulting * executable to be covered by the GNU General Public License. This exception does not * however invalidate any other reasons why the executable file might be covered by the * GNU General Public License. * * THE SOFTWARE IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY * WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * See config.h for a Config system overview and a bunch of details. */ #include "g2core.h" // #1 #include "config.h" // #2 #include "report.h" #include "controller.h" #include "canonical_machine.h" #include "json_parser.h" #include "text_parser.h" #include "persistence.h" #include "hardware.h" #include "help.h" #include "util.h" #include "xio.h" static void _set_defa(nvObj_t *nv, bool print); /*********************************************************************************** **** STRUCTURE ALLOCATIONS ******************************************************** ***********************************************************************************/ nvStr_t nvStr; nvList_t nvl; /*********************************************************************************** **** CODE ************************************************************************* ***********************************************************************************/ /* Primary access points to functions bound to text mode / JSON functions * These gatekeeper functions check index ranges so others don't have to * * nv_set() - Write a value or invoke a function - operates on single valued elements or groups * nv_get() - Build a nvObj with the values from the target & return the value * Populate nv body with single valued elements or groups (iterates) * nv_print() - Output a formatted string for the value. * nv_persist() - persist value to non-volatile storage. Takes special cases into account */ stat_t nv_set(nvObj_t *nv) { if (nv->index >= nv_index_max()) { return(STAT_INTERNAL_RANGE_ERROR); } return (((fptrCmd)cfgArray[nv->index].set)(nv)); } stat_t nv_get(nvObj_t *nv) { if (nv->index >= nv_index_max()) { return(STAT_INTERNAL_RANGE_ERROR); } return (((fptrCmd)cfgArray[nv->index].get)(nv)); } void nv_print(nvObj_t *nv) { if (nv->index >= nv_index_max()) { return; } ((fptrCmd)cfgArray[nv->index].print)(nv); } stat_t nv_persist(nvObj_t *nv) { if (nv_index_lt_groups(nv->index) == false) { return(STAT_INTERNAL_RANGE_ERROR); } if (GET_TABLE_BYTE(flags) & F_PERSIST) { return(write_persistent_value(nv)); } return (STAT_OK); } /************************************************************************************ * config_init() - called once on hard reset * * Performs one of 2 actions: * (1) if persistence is set up or out-of-rev load RAM and NVM with settings.h defaults * (2) if persistence is set up and at current config version use NVM data for config * * You can assume the cfg struct has been zeroed by a hard reset. * Do not clear it as the version and build numbers have already been set by tg_init() * * NOTE: Config assertions are handled from the controller */ void config_init() { nvObj_t *nv = nv_reset_nv_list(); config_init_assertions(); js.json_mode = JSON_MODE; // initial value until persistence is read cm_set_units_mode(MILLIMETERS); // must do inits in millimeter mode nv->index = 0; // this will read the first record in NVM read_persistent_value(nv); if (fp_NE(nv->value_flt, G2CORE_FIRMWARE_BUILD)) { // case (1) NVM is not setup or not in revision _set_defa(nv, false); } else { for (nv->index=0; nv_index_is_single(nv->index); nv->index++) { if (GET_TABLE_BYTE(flags) & F_INITIALIZE) { strncpy(nv->token, cfgArray[nv->index].token, TOKEN_LEN); // read the token from the array read_persistent_value(nv); nv_set(nv); } } sr_init_status_report(); // reset status reports } rpt_print_loading_configs_message(); } /* * set_defaults() - reset persistence with default values for machine profile * _set_defa() - helper function and called directly from config_init() */ static void _set_defa(nvObj_t *nv, bool print) { cm_set_units_mode(MILLIMETERS); // must do inits in MM mode for (nv->index=0; nv_index_is_single(nv->index); nv->index++) { if (cfgArray[nv->index].flags & F_INITIALIZE) { auto type = cfgArray[nv->index].flags & F_TYPE_MASK; if ((type == TYPE_INTEGER) || (type == TYPE_DATA)) { nv->valuetype = TYPE_INTEGER; nv->value_int = cfgArray[nv->index].def_value; } else if (type == TYPE_BOOLEAN) { nv->valuetype = TYPE_BOOLEAN; nv->value_int = cfgArray[nv->index].def_value; } else { nv->valuetype = TYPE_FLOAT; nv->value_flt = cfgArray[nv->index].def_value; } strncpy(nv->token, cfgArray[nv->index].token, TOKEN_LEN); cfgArray[nv->index].set(nv); // run the set method, nv_set(nv); if (nv->index == 0 || cfgArray[nv->index].flags & F_PERSIST) { nv_persist(nv); } } } sr_init_status_report(); // reset status reports if (print) { rpt_print_initializing_message(); // don't start TX until all the NVM persistence is done } } stat_t set_defaults(nvObj_t *nv) { // failsafe. nv->value_int must be true or no action occurs if (!nv->value_int) { return(help_defa(nv)); } _set_defa(nv, true); // The nvlist was used for the initialize message so the values are all garbage // Mark the nv as $defa so it displays nicely in the response nv_reset_nv_list(); strncpy(nv->token, "defa", TOKEN_LEN); // nv->index = nv_get_index("", nv->token); // correct, but not required nv->valuetype = TYPE_INTEGER; // ++++ probably should be TYPE_BOOLEAN nv->value_int = true; return (STAT_OK); } /* * config_init_assertions() * config_test_assertions() - check memory integrity of config sub-system */ void config_init_assertions() { cfg.magic_start = MAGICNUM; cfg.magic_end = MAGICNUM; nvl.magic_start = MAGICNUM; nvl.magic_end = MAGICNUM; nvStr.magic_start = MAGICNUM; nvStr.magic_end = MAGICNUM; } stat_t config_test_assertions() { if ((BAD_MAGIC(cfg.magic_start)) || (BAD_MAGIC(cfg.magic_end)) || (BAD_MAGIC(nvl.magic_start)) || (BAD_MAGIC(nvl.magic_end)) || (BAD_MAGIC(nvStr.magic_start)) || (BAD_MAGIC(nvStr.magic_end))) { return(cm_panic(STAT_CONFIG_ASSERTION_FAILURE, "config_test_assertions()")); } return (STAT_OK); } /***** Generic Internal Functions *********************************************/ /* Generic gets() * get_nul() - get nothing (returns STAT_NOOP) * get_int32() - get value as 32 bit integer * get_data() - get value as 32 bit integer blind cast * get_flt() - get value as float */ stat_t get_nul(nvObj_t *nv) { nv->valuetype = TYPE_NULL; return (STAT_NOOP); } stat_t get_int32(nvObj_t *nv) { nv->value_int = *((int32_t *)GET_TABLE_WORD(target)); nv->valuetype = TYPE_INTEGER; return (STAT_OK); } stat_t get_flt(nvObj_t *nv) { nv->value_flt= *((float *)GET_TABLE_WORD(target)); nv->precision = (int8_t)GET_TABLE_WORD(precision); nv->valuetype = TYPE_FLOAT; return (STAT_OK); } stat_t get_data(nvObj_t *nv) { nv->value_int = *((uint32_t *)GET_TABLE_WORD(target)); nv->valuetype = TYPE_DATA; return (STAT_OK); } /* Generic sets() * set_noop() - set nothing and return OK * set_nul() - set nothing and return READ_ONLY error * set_ro() - set nothing, return read-only error * set_int32() - set value as 32 bit unsigned integer * set_flt() - set value as float * set_data() - set value as 32 bit integer blind cast */ stat_t set_noop(nvObj_t *nv) { // we call this for msg to parrot back the string // check for string type and pass-through if (cfgArray[nv->index].flags & TYPE_STRING) { nv->valuetype = TYPE_STRING; } else { nv->valuetype = TYPE_NULL; } return (STAT_OK); // hack until JSON is refactored } stat_t set_nul(nvObj_t *nv) { nv->valuetype = TYPE_NULL; return (STAT_PARAMETER_IS_READ_ONLY); // this is what it should be } stat_t set_ro(nvObj_t *nv) { if (strcmp(nv_body->token, "sr") == 0) { // hack. If setting an SR it doesn't fail return (STAT_OK); } nv->valuetype = TYPE_NULL; return (STAT_PARAMETER_IS_READ_ONLY); } stat_t set_int32(nvObj_t *nv) { *((int32_t *)GET_TABLE_WORD(target)) = nv->value_int; nv->valuetype = TYPE_INTEGER; return(STAT_OK); } stat_t set_flt(nvObj_t *nv) { *((float *)GET_TABLE_WORD(target)) = nv->value_flt; nv->precision = GET_TABLE_WORD(precision); nv->valuetype = TYPE_FLOAT; return(STAT_OK); } stat_t set_data(nvObj_t *nv) { uint32_t *v = (uint32_t*)&nv->value_int; *((uint32_t *)GET_TABLE_WORD(target)) = *v; nv->valuetype = TYPE_DATA; return(STAT_OK); } /************************************************************************************ * Group operations * * Group operations work on parent/child groups where the parent is one of: * axis group x,y,z,a,b,c * motor group 1,2,3,4 * PWM group p1 * coordinate group g54,g55,g56,g57,g58,g59,g92 * system group "sys" - a collection of otherwise unrelated variables * * Text mode can only GET groups. For example: * $x get all members of an axis group * $1 get all members of a motor group * $ get any named group from the above lists * * In JSON groups are carried as parent / child objects & can get and set elements: * {"x":""} get all X axis parameters * {"x":{"vm":""}} get X axis velocity max * {"x":{"vm":1000}} set X axis velocity max * {"x":{"vm":"","fr":""}} get X axis velocity max and feed rate * {"x":{"vm":1000,"fr";900}} set X axis velocity max and feed rate * {"x":{"am":1,"fr":800,....}} set multiple or all X axis parameters */ /* * get_grp() - read data from axis, motor, system or other group * * get_grp() is a group expansion function that expands the parent group and returns * the values of all the children in that group. It expects the first nvObj in the * nvBody to have a valid group name in the token field. This first object will be set * to a TYPE_PARENT. The group field of the first nvOBJ is left nul - as the group * field refers to a parent group, which this group has none. * * All subsequent nvObjs in the body will be populated with their values. * The token field will be populated as will the parent name in the group field. * * The sys group is an exception where the children carry a blank group field, even though * the sys parent is labeled as a TYPE_PARENT. */ stat_t get_grp(nvObj_t *nv) { char group[GROUP_LEN+1]; strcpy(group, nv->token); // save the group string nv_reset_nv_list(); // start with a clean list strcpy(nv->token, group); // re-write the group string nv->valuetype = TYPE_PARENT; // make first object the parent for (index_t i=0; nv_index_is_single(i); i++) { if (strcmp(group, cfgArray[i].group) != 0) { continue; } (++nv)->index = i; nv_get_nvObj(nv); } return (STAT_OK); } /* * set_grp() - get or set one or more values in a group * * This functions is called "_set_group()" but technically it's a getter and * a setter. It iterates the group children and either gets the value or sets * the value for each depending on the nv->valuetype. * * This function serves JSON mode only as text mode shouldn't call it. */ stat_t set_grp(nvObj_t *nv) { if (js.json_mode == TEXT_MODE) { return (STAT_UNRECOGNIZED_NAME); } for (uint8_t i=0; inx) == NULL) break; if (nv->valuetype == TYPE_EMPTY) break; else if (nv->valuetype == TYPE_NULL) // NULL means GET the value nv_get(nv); else { nv_set(nv); nv_persist(nv); } } return (STAT_OK); } /*********************************************************************************** ***** nvObj functions ************************************************************ ***********************************************************************************/ /*********************************************************************************** * nvObj helper functions and other low-level nv helpers */ /* nv_get_index() - get index from mnenonic token + group * * nv_get_index() is the most expensive routine in the whole config. It does a * linear table scan of the strings, which of course could be further * optimized with indexes or hashing. */ index_t nv_get_index(const char *group, const char *token) { return cfgArray.getIndex(group, token); // char c; // char str[TOKEN_LEN + GROUP_LEN+1]; // should actually never be more than TOKEN_LEN+1 // strncpy(str, group, GROUP_LEN+1); // strncat(str, token, TOKEN_LEN+1); // index_t i; // index_t index_max = nv_index_max(); // for (i=0; i < index_max; i++) { // auto config = cfgArray[i]; // if ((c = config.token[0]) != str[0]) { continue; } // 1st character mismatch // if ((c = config.token[1]) == NUL) { if (str[1] == NUL) return(i); } // one character match // if (c != str[1]) continue; // 2nd character mismatch // if ((c = config.token[2]) == NUL) { if (str[2] == NUL) return(i); } // two character match // if (c != str[2]) continue; // 3rd character mismatch // if ((c = config.token[3]) == NUL) { if (str[3] == NUL) return(i); } // three character match // if (c != str[3]) continue; // 4th character mismatch // if ((c = config.token[4]) == NUL) { if (str[4] == NUL) return(i); } // four character match // if (c != str[4]) continue; // 5th character mismatch // if ((c = config.token[5]) == NUL) { if (str[5] == NUL) return(i); } // four character match // if (c != str[5]) continue; // 6th character mismatch // if ((c = config.token[6]) == NUL) { if (str[6] == NUL) return(i); } // four character match // if (c != str[6]) continue; // 7th character mismatch // if ((c = config.token[7]) == NUL) { if (str[7] == NUL) return(i); } // four character match // if (c != str[7]) continue; // 8th character mismatch // if ((c = config.token[8]) == NUL) { if (str[8] == NUL) return(i); } // four character match // if (c != str[8]) continue; // 9th character mismatch // return (i); // five character match // } // return (NO_MATCH); } /* * nv_get_type() - returns command type as a NV_TYPE enum * * Note: Exception reports (er) do not go through this mechanism so they are * not in the list below */ uint8_t nv_get_type(nvObj_t *nv) { if (nv->token[0] == NUL) return (NV_TYPE_NULL); if (strcmp("gc", nv->token) == 0) { return (NV_TYPE_GCODE); } if (strcmp("n", nv->token) == 0) { return (NV_TYPE_LINENUM); } if (strcmp("sr", nv->token) == 0) { return (NV_TYPE_REPORT); } if (strcmp("qr", nv->token) == 0) { return (NV_TYPE_REPORT); } if (strcmp("msg",nv->token) == 0) { return (NV_TYPE_MESSAGE); } if (strcmp("err",nv->token) == 0) { return (NV_TYPE_MESSAGE); } // errors are reported as messages return (NV_TYPE_CONFIG); } /* * nv_coerce_types() - change types based on type field in configApp table */ void nv_coerce_types(nvObj_t *nv) { if (nv->valuetype == TYPE_NULL) { // don't change type if it's a GET query return; } valueType type = (valueType)(cfgArray[nv->index].flags & F_TYPE_MASK); // if the data type should be integer, and we got a float, round it // note that we keep the original float value around if (type == TYPE_INTEGER) { if (nv->valuetype == TYPE_FLOAT) { nv->value_int = std::floor(nv->value_flt+0.5f); } nv->valuetype = TYPE_INTEGER; // will pay attention to the int value, not the float } else if (type == TYPE_BOOLEAN) { // it may have been marked as a boolean, but if it's not... if (nv->valuetype == TYPE_INTEGER) { nv->value_int = nv->value_int ? true : false; } else if (nv->valuetype == TYPE_FLOAT) { nv->value_int = (fp_ZERO(nv->value_flt)) ? false : true; } nv->valuetype = TYPE_BOOLEAN; } } /****************************************************************************** * nvObj low-level object and list operations * nv_get_nvObj() - setup a nv object by providing the index * nv_reset_nv() - quick clear for a new nv object * nv_reset_nv_list() - clear entire header, body and footer for a new use * nv_copy_string() - used to write a string to shared string storage and link it * nv_add_object() - write contents of parameter to first free object in the body * nv_add_integer() - add an integer value to end of nv body (Note 1) * nv_add_float() - add a floating point value to end of nv body * nv_add_string() - add a string object to end of nv body * nv_add_conditional_message() - add a message to nv body if messages are enabled * * Note: Functions that return a nv pointer point to the object that was modified or * a NULL pointer if there was an error. * * Note: Adding a really large integer (like a checksum value) may lose precision due * to the cast to a float. Sometimes it's better to load an integer as a string if * all you want to do is display it. * * Note: A trick is to cast all string constants for nv_copy_string(), nv_add_object(), * nv_add_string() and nv_add_conditional_message() to (const char *). Examples: * * nv_add_string((const char *)"msg", string); */ void nv_get_nvObj(nvObj_t *nv) { if (nv->index >= nv_index_max()) { return; } // sanity index_t tmp = nv->index; nv_reset_nv(nv); nv->index = tmp; strcpy(nv->token, cfgArray[nv->index].token); // token field is always terminated strcpy(nv->group, cfgArray[nv->index].group); // group field is always terminated // special processing for system groups and stripping tokens for groups if (nv->group[0] != NUL) { if (cfgArray[nv->index].flags & F_NOSTRIP) { nv->group[0] = NUL; } else { strcpy(nv->token, &nv->token[strlen(nv->group)]); // strip group from the token } } ((fptrCmd)cfgArray[nv->index].get)(nv); // populate the value } nvObj_t *nv_reset_nv(nvObj_t *nv) // clear a single nvObj structure { nv->valuetype = TYPE_EMPTY; // selective clear is much faster than calling memset nv->index = 0; nv->value_int = 0; nv->value_flt = 0; nv->precision = 0; nv->token[0] = NUL; nv->group[0] = NUL; nv->stringp = NULL; if (nv->pv == NULL) { // set depth correctly nv->depth = 0; } else { if (nv->pv->valuetype == TYPE_PARENT) { nv->depth = nv->pv->depth + 1; } else { nv->depth = nv->pv->depth; } } return (nv); // return pointer to nv as a convenience to callers } void _nv_reset_a_list(nvObj_t *nv, uint8_t length) // clear some nv list (called from below) { for (uint8_t i=0; ipv = (nv-1); // the ends are bogus & corrected later nv->nx = (nv+1); nv->index = 0; nv->depth = 1; // header and footer are corrected later nv->precision = 0; nv->valuetype = TYPE_EMPTY; nv->token[0] = NUL; } (--nv)->nx = NULL; } nvObj_t *nv_reset_nv_list() // clear the header and response body { nvStr.wp = 0; // reset the shared string nvObj_t *nv = nvl.list; // set up linked list and initialize elements _nv_reset_a_list(nv, NV_LIST_LEN); nv = nvl.list; // setup response header element ('r') nv->pv = NULL; nv->depth = 0; nv->valuetype = TYPE_PARENT; strcpy(nv->token, "r"); return (nv_body); // this is a convenience for calling routines } nvObj_t *nv_reset_exec_nv_list() // clear the exec body { nvObj_t *nv = nv_exec; _nv_reset_a_list(nv, NV_EXEC_LEN); return (nv_exec); // this is a convenience for calling routines } stat_t nv_copy_string(nvObj_t *nv, const char *src) { if ((nvStr.wp + strlen(src)) > NV_SHARED_STRING_LEN) { return (STAT_BUFFER_FULL); } char *dst = &nvStr.string[nvStr.wp]; strcpy(dst, src); // copy string to current head position // string has already been tested for overflow, above nvStr.wp += strlen(src)+1; // advance head for next string nv->stringp = (char (*)[])dst; return (STAT_OK); } nvObj_t *nv_add_object(const char *token) // add an object to the body using a token { nvObj_t *nv = nv_body; for (uint8_t i=0; ivaluetype != TYPE_EMPTY) { if ((nv = nv->nx) == NULL) { // not supposed to find a NULL; here for safety return(NULL); } continue; } // load the index from the token or die trying if ((nv->index = nv_get_index((const char *)"",token)) == NO_MATCH) { return (NULL);} nv_get_nvObj(nv); // populate the object from the index return (nv); } return (NULL); } nvObj_t *nv_add_integer(const char *token, const int32_t value) // add an integer object to the body { nvObj_t *nv = nv_body; for (uint8_t i=0; ivaluetype != TYPE_EMPTY) { if ((nv = nv->nx) == NULL) { // not supposed to find a NULL; here for safety return(NULL); } continue; } strncpy(nv->token, token, TOKEN_LEN); nv->value_int = value; nv->valuetype = TYPE_INTEGER; return (nv); } return (NULL); } nvObj_t *nv_add_data(const char *token, const uint32_t value)// add an data object to the body { nvObj_t *nv = nv_body; for (uint8_t i=0; ivaluetype != TYPE_EMPTY) { if ((nv = nv->nx) == NULL) { // not supposed to find a NULL; here for safety return(NULL); } continue; } strcpy(nv->token, token); nv->value_int = value; nv->valuetype = TYPE_DATA; return (nv); } return (NULL); } nvObj_t *nv_add_float(const char *token, const float value) // add a float object to the body { nvObj_t *nv = nv_body; for (uint8_t i=0; ivaluetype != TYPE_EMPTY) { if ((nv = nv->nx) == NULL) { // not supposed to find a NULL; here for safety return(NULL); } continue; } strncpy(nv->token, token, TOKEN_LEN); nv->value_flt = value; nv->valuetype = TYPE_FLOAT; return (nv); } return (NULL); } nvObj_t *nv_add_string(const char *token, const char *string) // add a string object to the body { nvObj_t *nv = nv_body; for (uint8_t i=0; ivaluetype != TYPE_EMPTY) { if ((nv = nv->nx) == NULL) { // not supposed to find a NULL; here for safety return(NULL); } continue; } strncpy(nv->token, token, TOKEN_LEN); if (nv_copy_string(nv, string) != STAT_OK) { return (NULL); } nv->index = nv_get_index((const char *)"", nv->token); nv->valuetype = TYPE_STRING; return (nv); } return (NULL); } /* * nv_add__conditional_message() - queue a RAM string as a message in the response (conditionally) */ nvObj_t *nv_add_conditional_message(const char *string) // conditionally add a message object to the body { if ((js.json_mode == JSON_MODE) && (js.echo_json_messages != true)) { return (NULL); } return(nv_add_string((const char *)"msg", string)); } /**** nv_print_list() - print nv_array as JSON or text ********************** * * Generate and print the JSON and text mode output strings. Use this function * for all text and JSON output that wants to be in a response header. * Don't just printf stuff. * * Inputs: * json_flags = JSON_OBJECT_FORMAT - print just the body w/o header or footer * json_flags = JSON_RESPONSE_FORMAT - print a full "r" object with footer * json_flags = JSON_RESPONSE_TO_MUTED_FORMAT - JSON_RESPONSE_FORMAT, but only to muted channels * * text_flags = TEXT_INLINE_PAIRS - print text as name/value pairs on a single line * text_flags = TEXT_INLINE_VALUES - print text as comma separated values on a single line * text_flags = TEXT_MULTILINE_FORMATTED - print text one value per line with formatting string */ void nv_print_list(stat_t status, uint8_t text_flags, uint8_t json_flags) { if ((js.json_mode == JSON_MODE) || (js.json_mode == MARLIN_COMM_MODE)) { json_print_list(status, json_flags); } else { text_print_list(status, text_flags); } } /**************************************************************************** ***** Diagnostics ********************************************************** ****************************************************************************/ void nv_dump_nv(nvObj_t *nv) { sprintf (cs.out_buf, "i:%ld, d:%d, t:%d, p:%d, v:%f, g:%s, t:%s, s:%s\n", nv->index, nv->depth, nv->valuetype, nv->precision, (double)nv->value_flt, // would need to add in value_int to be complete nv->group, nv->token, (char *)nv->stringp); xio_writeline(cs.out_buf); }