mirror of
https://github.com/synthetos/g2.git
synced 2026-02-05 10:39:53 +08:00
773 lines
30 KiB
C++
773 lines
30 KiB
C++
/*
|
|
* report.cpp - Status reports and other reporting functions
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "g2core.h"
|
|
#include "config.h"
|
|
#include "report.h"
|
|
#include "controller.h"
|
|
#include "json_parser.h"
|
|
#include "text_parser.h"
|
|
#include "planner.h"
|
|
#include "settings.h"
|
|
#include "util.h"
|
|
#include "xio.h"
|
|
|
|
|
|
/**** Allocation ****/
|
|
|
|
srSingleton_t sr;
|
|
qrSingleton_t qr;
|
|
|
|
/**** Exception Reports ************************************************************
|
|
*
|
|
* rpt_exception() - generate an exception message - always in JSON format
|
|
*
|
|
* Returns incoming status value and a message to the exception.
|
|
* Do not use global_string_buf[] for *msg or it will get trampled.
|
|
*
|
|
* WARNING: Do not call this function from MED or HI interrupts (LO is OK)
|
|
* or there is a potential for deadlock in the TX buffer.
|
|
*/
|
|
|
|
stat_t rpt_exception(stat_t status, const char *msg)
|
|
{
|
|
if (status != STAT_OK) { // makes it possible to call exception reports w/o checking status value
|
|
|
|
// you cannot send an exception report if the USB has not been set up. Causes a processor exception.
|
|
if (cs.controller_state >= CONTROLLER_READY) {
|
|
char buffer[128];
|
|
sprintf(buffer, "{\"er\":{\"fb\":%0.2f,\"st\":%d,\"msg\":\"%s - %s\"}}\n",
|
|
G2CORE_FIRMWARE_BUILD, status, get_status_message(status), msg);
|
|
xio_writeline(buffer);
|
|
}
|
|
}
|
|
return (status); // makes it possible to inline, e.g: return(rpt_exception(status));
|
|
}
|
|
|
|
/*
|
|
* rpt_er() - send a bogus exception report for testing purposes (it's not real)
|
|
*/
|
|
stat_t rpt_er(nvObj_t *nv)
|
|
{
|
|
return(rpt_exception(STAT_GENERIC_EXCEPTION_REPORT, "bogus exception report")); // bogus exception report for testing
|
|
}
|
|
|
|
/**** Application Messages *********************************************************
|
|
* rpt_print_initializing_message() - initializing configs from hard-coded profile
|
|
* rpt_print_loading_configs_message() - loading configs from EEPROM
|
|
* rpt_print_system_ready_message() - system ready message
|
|
*
|
|
* These messages are always in JSON format to allow UIs to sync
|
|
*/
|
|
|
|
void _startup_helper(stat_t status, const char *msg)
|
|
{
|
|
#ifndef __SUPPRESS_STARTUP_MESSAGES
|
|
nv_reset_nv_list();
|
|
nv_add_object("fv"); // firmware version
|
|
nv_add_object("fb"); // firmware build
|
|
nv_add_object("fbs"); // firmware build string
|
|
nv_add_object("fbc"); // settings configuration file used
|
|
nv_add_object("hp"); // hardware platform
|
|
nv_add_object("hv"); // hardware version
|
|
nv_add_object("id"); // hardware ID
|
|
nv_add_string("msg",msg); // startup message
|
|
json_print_response(status);
|
|
#endif
|
|
}
|
|
|
|
void rpt_print_initializing_message(void)
|
|
{
|
|
_startup_helper(STAT_INITIALIZING, INIT_MESSAGE);
|
|
}
|
|
|
|
void rpt_print_loading_configs_message(void)
|
|
{
|
|
_startup_helper(STAT_INITIALIZING, "Loading configs from EEPROM");
|
|
}
|
|
|
|
void rpt_print_system_ready_message(void)
|
|
{
|
|
// If in Marlin mode and connected as Marlin do not send a startup message
|
|
#if MARLIN_COMPAT_ENABLED == true
|
|
if (MARLIN_COMM_MODE != js.json_mode) {
|
|
_startup_helper(STAT_OK, "SYSTEM READY");
|
|
}
|
|
#else
|
|
_startup_helper(STAT_OK, "SYSTEM READY");
|
|
#endif
|
|
if (cs.comm_mode == TEXT_MODE) { text_response(STAT_OK, (char *)"");}// prompt
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Status Reports
|
|
*
|
|
* Status report behaviors
|
|
*
|
|
* Configuration:
|
|
*
|
|
* Status reports are configurable only from JSON. SRs are configured
|
|
* by sending a status report SET object, e.g:
|
|
*
|
|
* {"sr":{"line":true,"posx":true,"posy":true....."motm":true,"stat":true}}
|
|
*
|
|
* Status report formats: The following formats exist for status reports:
|
|
*
|
|
* - JSON format: Returns a JSON object as above, but with the values filled in.
|
|
* In JSON form all values are returned as numeric values or enumerations.
|
|
* E.g. "posx" is returned as 124.523 and "unit" is returned as 0 for
|
|
* inches (G20) and 1 for mm (G21).
|
|
*
|
|
* - CSV format: Returns a single line of comma separated token:value pairs.
|
|
* Values are returned as numeric values or English text.
|
|
* E.g. "posx" is still returned as 124.523 but "unit" is returned as
|
|
* "inch" for inches (G20) and "mm" for mm (G21).
|
|
*
|
|
* - Multi-line format: Returns a multi-line report where each value occupies
|
|
* one line. Each line contains explanatory English text. Enumerated values are
|
|
* returned as English text as per CSV form.
|
|
*
|
|
* Status report invocation: Status reports can be invoked in the following ways:
|
|
*
|
|
* - Ad-hoc request in JSON mode. Issue {"sr":""} (or equivalent). Returns a
|
|
* JSON format report (wrapped in a response header, of course).
|
|
*
|
|
* - Automatic status reports in JSON mode. Returns JSON format reports
|
|
* according to "si" setting.
|
|
*
|
|
* - Ad-hoc request in text mode. Triggered by sending ?<cr>. Returns status
|
|
* report in multi-line format. Additionally, a line starting with ? will put
|
|
* the system into text mode.
|
|
*
|
|
* - Automatic status reports in text mode return CSV format according to si setting
|
|
*/
|
|
static stat_t _populate_unfiltered_status_report(void);
|
|
static uint8_t _populate_filtered_status_report(void);
|
|
|
|
uint8_t _is_stat(nvObj_t *nv)
|
|
{
|
|
char token[TOKEN_LEN+1];
|
|
|
|
GET_TOKEN_STRING(nv->value_int, token); // pass in index, get back token
|
|
if (strcmp(token, "stat") == 0) { return (true);}
|
|
return (false);
|
|
}
|
|
|
|
/*
|
|
* sr_init_status_report()
|
|
*
|
|
* Call this function to completely re-initialize the status report
|
|
* Sets SR list to hard-coded defaults and re-initializes SR values in NVM
|
|
*/
|
|
|
|
void sr_init_status_report()
|
|
{
|
|
nvObj_t *nv = nv_reset_nv_list(); // used for status report persistence locations
|
|
sr.status_report_request = SR_OFF;
|
|
char sr_defaults[NV_STATUS_REPORT_LEN][TOKEN_LEN+1] = { STATUS_REPORT_DEFAULTS };
|
|
nv->index = nv_get_index((const char *)"", (char *)"se00"); // set first SR persistence index
|
|
|
|
// record the index of the "stat" variable so we can use it during reporting
|
|
sr.stat_index = nv_get_index((const char *)"", (const char *)"stat");
|
|
|
|
// setup the status report array
|
|
for (uint8_t i=0; i < NV_STATUS_REPORT_LEN ; i++) {
|
|
if (sr_defaults[i][0] == NUL) break; // quit on first blank array entry
|
|
sr.status_report_list[i].value = -1234567; // pre-load values with an unlikely number
|
|
|
|
nv->value_int = nv_get_index((const char *)"", sr_defaults[i]);// load the index for the SR element
|
|
if (nv->value_int == NO_MATCH) {
|
|
rpt_exception(STAT_BAD_STATUS_REPORT_SETTING, "sr_init_status_report() encountered bad SR setting"); // trap mis-configured profile settings
|
|
return;
|
|
}
|
|
|
|
auto &cfgTmp = cfgArray[nv->value_int];
|
|
sr.status_report_list[i].index = nv->value_int;
|
|
sr.status_report_list[i].get = cfgTmp.get;
|
|
// sr.status_report_list[i].flags = cfgTmp.flags;
|
|
sr.status_report_list[i].precision = cfgTmp.precision;
|
|
strcpy(sr.status_report_list[i].group, cfgTmp.group);
|
|
strcpy(sr.status_report_list[i].token, cfgTmp.token);
|
|
|
|
// special processing for system groups and stripping tokens for groups
|
|
if (cfgTmp.group[0] != NUL) {
|
|
if (cfgArray[nv->index].flags & F_NOSTRIP) {
|
|
sr.status_report_list[i].group[0] = NUL;
|
|
strcpy(sr.status_report_list[i].token, cfgTmp.token);
|
|
} else {
|
|
strcpy(sr.status_report_list[i].group, cfgTmp.group);
|
|
strcpy(sr.status_report_list[i].token, &cfgTmp.token[strlen(cfgTmp.group)]); // strip group from the token
|
|
}
|
|
}
|
|
|
|
nv_set(nv);
|
|
// nv_persist(nv); // conditionally persist - automatic by nv_persist()
|
|
nv->index++; // increment SR NVM index
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sr_set_status_report() - read a list of NV pairs to set up SRs and return a report
|
|
*
|
|
* Note: By the time this function is called any unrecognized tokens have been detected and
|
|
* rejected by the JSON or text parser. In other words, it should never get to here if
|
|
* there is an unrecognized token in the SR string.
|
|
*/
|
|
|
|
stat_t sr_set_status_report(nvObj_t *nv)
|
|
{
|
|
uint8_t elements = 0;
|
|
status_report_item status_report_list[NV_STATUS_REPORT_LEN];
|
|
memset(status_report_list, 0, sizeof(status_report_list));
|
|
index_t sr_start = nv_get_index((const char *)"",(const char *)"se00");// set first SR persistence index
|
|
|
|
for (uint8_t i=0; i<NV_STATUS_REPORT_LEN; i++) {
|
|
if (((nv = nv->nx) == NULL) || (nv->valuetype == TYPE_EMPTY)) {
|
|
break;
|
|
}
|
|
// Note: valuetype may have been coerced from boolean to something else, so just treat value_int as a bool
|
|
if (nv->value_int) {
|
|
auto &cfgTmp = cfgArray[nv->index];
|
|
|
|
status_report_list[i].index = nv->index;
|
|
status_report_list[i].get = cfgTmp.get;
|
|
// status_report_list[i].flags = cfgTmp.flags;
|
|
status_report_list[i].precision = cfgTmp.precision;
|
|
strcpy(status_report_list[i].group, cfgTmp.group);
|
|
strcpy(status_report_list[i].token, cfgTmp.token);
|
|
|
|
// special processing for system groups and stripping tokens for groups
|
|
if (cfgTmp.group[0] != NUL) {
|
|
if (cfgArray[nv->index].flags & F_NOSTRIP) {
|
|
status_report_list[i].group[0] = NUL;
|
|
strcpy(status_report_list[i].token, cfgTmp.token);
|
|
} else {
|
|
strcpy(status_report_list[i].group, cfgTmp.group);
|
|
strcpy(status_report_list[i].token, &cfgTmp.token[strlen(cfgTmp.group)]); // strip group from the token
|
|
}
|
|
}
|
|
nv->value_int = nv->index; // persist the index as the value
|
|
nv->index = sr_start + i; // index of the SR persistence location
|
|
nv_persist(nv);
|
|
elements++;
|
|
} else {
|
|
return (STAT_UNSUPPORTED_TYPE);
|
|
}
|
|
}
|
|
if (elements == 0) {
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
memcpy(sr.status_report_list, status_report_list, sizeof(status_report_list));
|
|
return(_populate_unfiltered_status_report()); // return current values
|
|
}
|
|
|
|
/*
|
|
* sr_request_status_report() - request a status report
|
|
*
|
|
* Status reports can be request from a number of sources including:
|
|
* - direct request from command line in the form of ? or {"sr:""}
|
|
* - timed requests during machining cycle
|
|
* - filtered request after each Gcode block
|
|
*
|
|
* Status reports are generally returned with minimal delay (from the controller callback),
|
|
* but will not be provided more frequently than the status report interval
|
|
*
|
|
* Requests can specify immediate or timed reports, and can also force a filtered or full report.
|
|
* See cmStatusReportRequest enum in report.h for details.
|
|
*/
|
|
|
|
stat_t sr_request_status_report(cmStatusReportRequest request_type)
|
|
{
|
|
// if (sr.status_report_request != SR_OFF) { // ignore multiple requests. First one wins.
|
|
// return (STAT_OK);
|
|
// }
|
|
|
|
// sr.status_report_systick.set(1);
|
|
if (request_type == SR_REQUEST_IMMEDIATE) {
|
|
// request a filtered report unless a verbose report has laready been requested
|
|
// verbosity setting may override and make it verbose anyway
|
|
sr.status_report_request = sr.status_report_request == SR_VERBOSE ? SR_VERBOSE : SR_FILTERED;
|
|
sr.status_report_systick.set(0, true); // immediate
|
|
|
|
} else if (request_type == SR_REQUEST_IMMEDIATE_FULL) {
|
|
//always trigger verbose report, regardless of verbosity setting
|
|
sr.status_report_request = SR_VERBOSE;
|
|
sr.status_report_systick.set(0, true); // immediate
|
|
|
|
} else if (request_type == SR_REQUEST_TIMED) {
|
|
// request a filtered report unless a verbose report has laready been requested
|
|
// verbosity setting may override and make it verbose anyway
|
|
sr.status_report_request = sr.status_report_request == SR_VERBOSE ? SR_VERBOSE : SR_FILTERED;
|
|
sr.status_report_systick.set(sr.status_report_interval, true); // true means to not extend the timer
|
|
|
|
} else {
|
|
//always trigger verbose report, regardless of verbosity setting
|
|
sr.status_report_request = SR_VERBOSE;
|
|
sr.status_report_systick.set(sr.status_report_interval, true); // true means to not extend the timer
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* sr_status_report_callback() - main loop callback to send a report if one is ready
|
|
*/
|
|
stat_t sr_status_report_callback() // called by controller dispatcher
|
|
{
|
|
// conditions where autogenerated SRs will not be returned
|
|
if ((sr.status_report_request == SR_OFF) ||
|
|
(sr.status_report_verbosity == SR_OFF) ||
|
|
(!sr.status_report_systick.isPast()) ||
|
|
(cs.controller_state != CONTROLLER_READY) ) {
|
|
return (STAT_NOOP);
|
|
}
|
|
|
|
// don't send an SR if you the planner is experiencing a time constraint
|
|
if (!mp_is_phat_city_time()) {
|
|
if (++sr.throttle_counter != SR_THROTTLE_COUNT) {
|
|
return (STAT_NOOP);
|
|
}
|
|
sr.throttle_counter = 0;
|
|
}
|
|
|
|
sr.status_report_request = SR_OFF;
|
|
// sr.status_report_systick.clear();
|
|
if ((sr.status_report_request == SR_VERBOSE) ||
|
|
(sr.status_report_verbosity == SR_VERBOSE)) {
|
|
_populate_unfiltered_status_report();
|
|
} else {
|
|
if (_populate_filtered_status_report() == false) { // no new data
|
|
return (STAT_OK);
|
|
}
|
|
}
|
|
nv_print_list(STAT_OK, TEXT_MULTILINE_FORMATTED, JSON_OBJECT_FORMAT);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* sr_run_text_status_report() - generate a text mode status report in multiline format
|
|
*/
|
|
stat_t sr_run_text_status_report()
|
|
{
|
|
_populate_unfiltered_status_report();
|
|
nv_print_list(STAT_OK, TEXT_MULTILINE_FORMATTED, JSON_RESPONSE_FORMAT);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* _populate_unfiltered_status_report() - populate nvObj body with status values
|
|
*
|
|
* Designed to be run as a response; i.e. have a "r" header and a footer.
|
|
*/
|
|
static stat_t _populate_unfiltered_status_report()
|
|
{
|
|
const char sr_str[] = "sr";
|
|
nvObj_t *nv = nv_reset_nv_list(); // sets *nv to the start of the body
|
|
|
|
nv->valuetype = TYPE_PARENT; // setup the parent object (no length checking required)
|
|
strcpy(nv->token, sr_str);
|
|
nv->index = nv_get_index((const char *)"", sr_str);// set the index - may be needed by calling function
|
|
nv = nv->nx; // no need to check for NULL as list has just been reset
|
|
|
|
for (uint8_t i=0; i<NV_STATUS_REPORT_LEN; i++) {
|
|
if (sr.status_report_list[i].index == 0) { // end of list
|
|
break;
|
|
}
|
|
// nv_get_nvObj(nv);
|
|
nv_reset_nv(nv);
|
|
nv->index = sr.status_report_list[i].index;
|
|
strcpy(nv->group, sr.status_report_list[i].group);
|
|
strcpy(nv->token, sr.status_report_list[i].token);
|
|
|
|
sr.status_report_list[i].get(nv);
|
|
|
|
strcpy(nv->token, sr.status_report_list[i].group);
|
|
strcpy(nv->token + strlen(sr.status_report_list[i].group), sr.status_report_list[i].token);
|
|
|
|
if ((nv = nv->nx) == NULL) {
|
|
return (cm_panic(STAT_BUFFER_FULL_FATAL, "_populate_unfiltered_status_report() sr link NULL")); // should never be NULL unless SR length exceeds available buffer array
|
|
}
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* _populate_filtered_status_report() - populate nvObj body with status values
|
|
*
|
|
* Designed to be displayed as a JSON object; i.e. no footer or header
|
|
* Returns 'true' if the report has new data, 'false' if there is nothing to report.
|
|
*
|
|
* NOTE: Unlike sr_populate_unfiltered_status_report(), this function does NOT set
|
|
* the SR index, which is a relatively expensive operation. In current use this
|
|
* doesn't matter, but if the caller assumes its set it may lead to a side-effect (bug)
|
|
*
|
|
* NOTE: Room for improvement - look up the SR index initially and cache it, use the
|
|
* cached value for all remaining reports.
|
|
*/
|
|
static uint8_t _populate_filtered_status_report()
|
|
{
|
|
const char sr_str[] = "sr";
|
|
bool has_data = false;
|
|
double current_value;
|
|
nvObj_t *nv = nv_reset_nv_list(); // sets nv to the start of the body
|
|
|
|
// Set thresholds to detect value changes based on precision for the value.
|
|
// Allow for floating point roundoffs, i.e. precision = 2 is 0.01 becomes --> 0.009
|
|
double precision[8] = { 0.9, 0.09, 0.009, 0.0009, 0.00009, 0.000009, 0.0000009, 0.00000009 };
|
|
|
|
nv->valuetype = TYPE_PARENT; // setup the parent object (no need to length check the copy)
|
|
strcpy(nv->token, sr_str);
|
|
// nv->index = nv_get_index((const char *)"", sr_str);// OMITTED - set the index - may be needed by calling function
|
|
nv = nv->nx; // no need to check for NULL as list has just been reset
|
|
|
|
for (uint8_t i=0; i<NV_STATUS_REPORT_LEN; i++) {
|
|
if (sr.status_report_list[i].index == 0) { // end of list
|
|
break;
|
|
}
|
|
// nv_get_nvObj(nv);
|
|
nv_reset_nv(nv);
|
|
nv->index = sr.status_report_list[i].index;
|
|
strcpy(nv->group, sr.status_report_list[i].group);
|
|
strcpy(nv->token, sr.status_report_list[i].token);
|
|
|
|
sr.status_report_list[i].get(nv);
|
|
|
|
bool changed = false;
|
|
|
|
// extract the value and cast into a float, regardless of value type
|
|
if (nv->valuetype == TYPE_FLOAT) {
|
|
current_value = nv->value_flt;
|
|
if ((fabs(current_value - sr.status_report_list[i].value) > precision[cfgArray[nv->index].precision])) {
|
|
changed = true;
|
|
}
|
|
} else {
|
|
auto current_value_int = nv->value_int;
|
|
if (((nv->index == sr.stat_index) &&
|
|
((current_value_int == COMBINED_PROGRAM_STOP) || (current_value_int == COMBINED_PROGRAM_END))) ||
|
|
(current_value_int != (decltype(current_value_int))sr.status_report_list[i].value)) {
|
|
changed = true;
|
|
current_value = current_value_int;
|
|
}
|
|
}
|
|
|
|
// report values that have changed by more than the indicated precision, but always stops and ends
|
|
if (changed) {
|
|
strcpy(nv->token, sr.status_report_list[i].group); // flatten out groups
|
|
strcat(nv->token + strlen(sr.status_report_list[i].group), sr.status_report_list[i].token);
|
|
|
|
sr.status_report_list[i].value = current_value;
|
|
|
|
if ((nv = nv->nx) == NULL) { // should never be NULL unless SR length exceeds available buffer array
|
|
return (false);
|
|
}
|
|
has_data = true;
|
|
} else {
|
|
nv->valuetype = TYPE_EMPTY; // filter this value out of the report
|
|
}
|
|
}
|
|
return (has_data);
|
|
}
|
|
|
|
|
|
/****************************
|
|
* END OF REPORT FUNCTIONS *
|
|
****************************/
|
|
|
|
/***********************************************************************************
|
|
* CONFIGURATION AND INTERFACE FUNCTIONS
|
|
* Functions to get and set variables from the cfgArray table
|
|
***********************************************************************************/
|
|
|
|
/*
|
|
* Wrappers and Setters - for calling from nvArray table
|
|
*
|
|
* sr_get() - run status report
|
|
* sr_set() - set status report elements
|
|
* sr_get_sv() - get status report verbosity
|
|
* sr_set_sv() - set status report verbosity
|
|
* sr_get_si() - get status report interval
|
|
* sr_set_si() - set status report interval
|
|
*/
|
|
|
|
stat_t sr_get(nvObj_t *nv) { return (_populate_unfiltered_status_report()); }
|
|
stat_t sr_set(nvObj_t *nv) { return (sr_set_status_report(nv)); }
|
|
|
|
stat_t sr_get_sv(nvObj_t *nv) { return(get_integer(nv, (uint8_t &)sr.status_report_verbosity)); }
|
|
stat_t sr_set_sv(nvObj_t *nv) { return(set_integer(nv, (uint8_t &)sr.status_report_verbosity, SR_OFF, SR_VERBOSE)); }
|
|
stat_t sr_get_si(nvObj_t *nv) { return(get_integer(nv, sr.status_report_interval)); }
|
|
stat_t sr_set_si(nvObj_t *nv) { return(set_int32(nv, sr.status_report_interval, STATUS_REPORT_MIN_MS, STATUS_REPORT_MAX_MS)); }
|
|
|
|
/*********************
|
|
* TEXT MODE SUPPORT *
|
|
*********************/
|
|
#ifdef __TEXT_MODE
|
|
|
|
static const char fmt_sv[] = "[sv] status report verbosity%6d [0=off,1=filtered,2=verbose]\n";
|
|
static const char fmt_si[] = "[si] status interval%14d ms\n";
|
|
|
|
void sr_print_sr(nvObj_t *nv) { _populate_unfiltered_status_report();}
|
|
void sr_print_sv(nvObj_t *nv) { text_print(nv, fmt_sv);}
|
|
void sr_print_si(nvObj_t *nv) { text_print(nv, fmt_si);}
|
|
|
|
#endif // __TEXT_MODE
|
|
|
|
|
|
/*****************************************************************************
|
|
* Queue Reports
|
|
*
|
|
* Queue reports can report three values:
|
|
* - qr queue depth - # of buffers availabel in planner queue
|
|
* - qi buffers added to planner queue since las report
|
|
* - qo buffers removed from planner queue since last report
|
|
*
|
|
* A QR_SINGLE report returns qr only. A QR_TRIPLE returns all 3 values
|
|
*
|
|
* There are 2 ways to get queue reports:
|
|
*
|
|
* 1. Enable single or triple queue reports using the QV variable. This will
|
|
* return a queue report every time the buffer depth changes
|
|
*
|
|
* 2. Add qr, qi and qo (or some combination) to the status report. This will
|
|
* return queue report data when status reports are generated.
|
|
*/
|
|
/*
|
|
* qr_init_queue_report() - initialize or clear queue report values
|
|
*/
|
|
|
|
void qr_init_queue_report()
|
|
{
|
|
qr.queue_report_requested = false;
|
|
qr.buffers_added = 0;
|
|
qr.buffers_removed = 0;
|
|
qr.init_tick = SysTickTimer.getValue(); // Uses C mapping of SysTickTimer.getValue();
|
|
}
|
|
|
|
/*
|
|
* qr_request_queue_report() - request a queue report
|
|
*
|
|
* Requests a queue report and also records the buffers added and removed
|
|
* since the last init (usually re-initted when a report is generated).
|
|
*/
|
|
|
|
void qr_request_queue_report(int8_t buffers)
|
|
{
|
|
// get buffer depth and added/removed count
|
|
qr.buffers_available = mp_get_planner_buffers(mp);
|
|
if (buffers > 0) {
|
|
qr.buffers_added += buffers;
|
|
} else {
|
|
qr.buffers_removed -= buffers;
|
|
}
|
|
|
|
// time-throttle requests while generating arcs
|
|
// qr.motion_mode = cm_get_motion_mode(ACTIVE_MODEL);
|
|
qr.motion_mode = cm_get_motion_mode((GCodeState_t *)&(cm->gm));
|
|
if ((qr.motion_mode == MOTION_MODE_CW_ARC) || (qr.motion_mode == MOTION_MODE_CCW_ARC)) {
|
|
uint32_t tick = SysTickTimer.getValue();
|
|
if (tick - qr.init_tick < MIN_ARC_QR_INTERVAL) {
|
|
qr.queue_report_requested = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// either return or request a report
|
|
if (qr.queue_report_verbosity != QR_OFF) {
|
|
qr.queue_report_requested = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* qr_queue_report_callback() - generate a queue report if one has been requested
|
|
*/
|
|
|
|
stat_t qr_queue_report_callback() // called by controller dispatcher
|
|
{
|
|
if ((qr.queue_report_verbosity == QR_OFF) ||
|
|
(js.json_verbosity == JV_SILENT) ||
|
|
(qr.queue_report_requested == false) ||
|
|
(!mp_is_phat_city_time())) {
|
|
return (STAT_NOOP);
|
|
}
|
|
|
|
qr.queue_report_requested = false;
|
|
|
|
char report[32]; // we know these reports can't be longer than 30 bytes
|
|
|
|
if (cs.comm_mode == TEXT_MODE) {
|
|
if (qr.queue_report_verbosity == QR_SINGLE) {
|
|
sprintf(report, "qr:%d\n", qr.buffers_available);
|
|
} else {
|
|
sprintf(report, "qr:%d, qi:%d, qo:%d\n", qr.buffers_available,qr.buffers_added,qr.buffers_removed);
|
|
}
|
|
} else {
|
|
if (qr.queue_report_verbosity == QR_SINGLE) {
|
|
sprintf(report, "{\"qr\":%d}\n", qr.buffers_available);
|
|
} else {
|
|
sprintf(report, "{\"qr\":%d,\"qi\":%d,\"qo\":%d}\n", qr.buffers_available, qr.buffers_added,qr.buffers_removed);
|
|
}
|
|
}
|
|
xio_writeline(report);
|
|
qr_init_queue_report();
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/* Alternate Formulation for a Single report - using nvObj list
|
|
|
|
// get a clean nv object
|
|
// nvObj_t *nv = nv_reset_nv_list(); // normally you do a list reset but the following is more time efficient
|
|
nvObj_t *nv = nv_body;
|
|
nv_reset_nv(nv);
|
|
nv->nx = NULL; // terminate the list
|
|
|
|
// make a qr object and print it
|
|
sprintf(nv->token, "qr");
|
|
nv->value = qr.buffers_available;
|
|
nv->valuetype = TYPE_INT;
|
|
nv_print_list(STAT_OK, TEXT_INLINE_PAIRS, JSON_OBJECT_FORMAT);
|
|
return (STAT_OK);
|
|
*/
|
|
|
|
/*
|
|
* Wrappers and Setters - for calling from cfgArray table
|
|
*
|
|
* qr_get() - run a queue report (as data)
|
|
* qi_get() - run a queue report - buffers in
|
|
* qo_get() - run a queue report - buffers out
|
|
*/
|
|
stat_t qr_get(nvObj_t *nv)
|
|
{
|
|
nv->value_int = mp_get_planner_buffers(mp); // ensure that manually requested QR count is always up to date
|
|
nv->valuetype = TYPE_INTEGER;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t qi_get(nvObj_t *nv)
|
|
{
|
|
nv->value_int = qr.buffers_added;
|
|
nv->valuetype = TYPE_INTEGER;
|
|
qr.buffers_added = 0; // reset it
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t qo_get(nvObj_t *nv)
|
|
{
|
|
nv->value_int = qr.buffers_removed;
|
|
nv->valuetype = TYPE_INTEGER;
|
|
qr.buffers_removed = 0; // reset it
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t qr_get_qv(nvObj_t *nv) { return(get_integer(nv, (uint8_t &)qr.queue_report_verbosity)); }
|
|
stat_t qr_set_qv(nvObj_t *nv) { return(set_integer(nv, (uint8_t &)qr.queue_report_verbosity, QR_OFF, QR_TRIPLE)); }
|
|
|
|
/*****************************************************************************
|
|
* JOB ID REPORTS
|
|
*
|
|
* job_populate_job_report()
|
|
* job_set_job_report()
|
|
* job_report_callback()
|
|
* job_get()
|
|
* job_set()
|
|
* job_print_job()
|
|
*/
|
|
|
|
stat_t job_populate_job_report()
|
|
{
|
|
const char job_str[] = "job";
|
|
char tmp[TOKEN_LEN+1];
|
|
nvObj_t *nv = nv_reset_nv_list(); // sets *nv to the start of the body
|
|
|
|
nv->valuetype = TYPE_PARENT; // setup the parent object
|
|
strcpy(nv->token, job_str);
|
|
|
|
//nv->index = nv_get_index((const char *)"", job_str);// set the index - may be needed by calling function
|
|
nv = nv->nx; // no need to check for NULL as list has just been reset
|
|
|
|
index_t job_start = nv_get_index((const char *)"",(const char *)"job1");// set first job persistence index
|
|
for (uint8_t i=0; i<4; i++) {
|
|
|
|
nv->index = job_start + i;
|
|
nv_get_nvObj(nv);
|
|
|
|
strcpy(tmp, nv->group); // concatenate groups and tokens - do NOT use strncpy()
|
|
strcat(tmp, nv->token);
|
|
strcpy(nv->token, tmp);
|
|
|
|
if ((nv = nv->nx) == NULL) return (STAT_OK); // should never be NULL unless SR length exceeds available buffer array
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t job_set_job_report(nvObj_t *nv)
|
|
{
|
|
index_t job_start = nv_get_index((const char *)"",(const char *)"job1");// set first job persistence index
|
|
|
|
for (uint8_t i=0; i<4; i++) {
|
|
if (((nv = nv->nx) == NULL) || (nv->valuetype == TYPE_EMPTY)) { break;}
|
|
if (nv->valuetype == TYPE_INTEGER) {
|
|
cfg.job_id[i] = nv->value_int;
|
|
nv->index = job_start + i; // index of the SR persistence location
|
|
nv_persist(nv);
|
|
} else {
|
|
return (STAT_UNSUPPORTED_TYPE);
|
|
}
|
|
}
|
|
job_populate_job_report(); // return current values
|
|
return (STAT_OK);
|
|
}
|
|
|
|
uint8_t job_report_callback()
|
|
{
|
|
if (cs.comm_mode == TEXT_MODE) {
|
|
// no-op, job_ids are client app state
|
|
return (STAT_OK);
|
|
}
|
|
sprintf(cs.out_buf, "{\"job\":[%lu,%lu,%lu,%lu]}\n", cfg.job_id[0], cfg.job_id[1], cfg.job_id[2], cfg.job_id[3] );
|
|
xio_writeline(cs.out_buf);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t job_get(nvObj_t *nv) { return (job_populate_job_report());}
|
|
stat_t job_set(nvObj_t *nv) { return (job_set_job_report(nv));}
|
|
void job_print_job(nvObj_t *nv) { job_populate_job_report();}
|
|
|
|
/*********************
|
|
* TEXT MODE SUPPORT *
|
|
*********************/
|
|
#ifdef __TEXT_MODE
|
|
|
|
static const char fmt_qr[] = "qr:%d\n";
|
|
static const char fmt_qi[] = "qi:%d\n";
|
|
static const char fmt_qo[] = "qo:%d\n";
|
|
static const char fmt_qv[] = "[qv] queue report verbosity%7d [0=off,1=single,2=triple]\n";
|
|
|
|
void qr_print_qr(nvObj_t *nv) { text_print(nv, fmt_qr);} // TYPE_INT
|
|
void qr_print_qi(nvObj_t *nv) { text_print(nv, fmt_qi);} // TYPE_INT
|
|
void qr_print_qo(nvObj_t *nv) { text_print(nv, fmt_qo);} // TYPE_INT
|
|
void qr_print_qv(nvObj_t *nv) { text_print(nv, fmt_qv);} // TYPE_INT
|
|
|
|
#endif // __TEXT_MODE
|