mirror of
https://github.com/synthetos/g2.git
synced 2026-10-14 10:52:33 +08:00
486 lines
19 KiB
C++
Executable File
486 lines
19 KiB
C++
Executable File
/*
|
|
* report.cpp - TinyG status report and other reporting functions.
|
|
* This file is part of the TinyG2 project
|
|
*
|
|
* Copyright (c) 2010 - 2013 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 "tinyg2.h"
|
|
#include "config.h"
|
|
#include "controller.h"
|
|
#include "report.h"
|
|
#include "json_parser.h"
|
|
#include "text_parser.h"
|
|
#include "planner.h"
|
|
#include "settings.h"
|
|
#include "hardware.h"
|
|
#include "util.h"
|
|
#include "xio.h" // for ASCII definitions
|
|
|
|
/*
|
|
#include "planner.h"
|
|
#include "gcode_parser.h"
|
|
#include "canonical_machine.h"
|
|
*/
|
|
|
|
#ifdef __cplusplus
|
|
extern "C"{
|
|
#endif
|
|
|
|
/**** Status and Exception Messages **************************************************
|
|
* rpt_get_status_message() - return the status message
|
|
* rpt_exception() - send an exception report (JSON formatted)
|
|
*
|
|
* These strings must align with the status codes in tinyg.h
|
|
* The number of elements in the indexing array must match the # of strings
|
|
*/
|
|
static const char_t stat_00[] = "OK";
|
|
static const char_t stat_01[] = "Error";
|
|
static const char_t stat_02[] = "Eagain";
|
|
static const char_t stat_03[] = "Noop";
|
|
static const char_t stat_04[] = "Complete";
|
|
static const char_t stat_05[] = "Terminated";
|
|
static const char_t stat_06[] = "Hard reset";
|
|
static const char_t stat_07[] = "End of line";
|
|
static const char_t stat_08[] = "End of file";
|
|
static const char_t stat_09[] = "File not open";
|
|
static const char_t stat_10[] = "Max file size exceeded";
|
|
static const char_t stat_11[] = "No such device";
|
|
static const char_t stat_12[] = "Buffer empty";
|
|
static const char_t stat_13[] = "Buffer full";
|
|
static const char_t stat_14[] = "Buffer full - fatal";
|
|
static const char_t stat_15[] = "Initializing";
|
|
static const char_t stat_16[] = "Entering boot loader";
|
|
static const char_t stat_17[] = "Function is stubbed";
|
|
static const char_t stat_18[] = "18";
|
|
static const char_t stat_19[] = "19";
|
|
|
|
static const char_t stat_20[] = "Internal error";
|
|
static const char_t stat_21[] = "Internal range error";
|
|
static const char_t stat_22[] = "Floating point error";
|
|
static const char_t stat_23[] = "Divide by zero";
|
|
static const char_t stat_24[] = "Invalid Address";
|
|
static const char_t stat_25[] = "Read-only address";
|
|
static const char_t stat_26[] = "Initialization failure";
|
|
static const char_t stat_27[] = "System shutdown";
|
|
static const char_t stat_28[] = "Memory corruption";
|
|
static const char_t stat_29[] = "29";
|
|
static const char_t stat_30[] = "30";
|
|
static const char_t stat_31[] = "31";
|
|
static const char_t stat_32[] = "32";
|
|
static const char_t stat_33[] = "33";
|
|
static const char_t stat_34[] = "34";
|
|
static const char_t stat_35[] = "35";
|
|
static const char_t stat_36[] = "36";
|
|
static const char_t stat_37[] = "37";
|
|
static const char_t stat_38[] = "38";
|
|
static const char_t stat_39[] = "39";
|
|
|
|
static const char_t stat_40[] = "Unrecognized command";
|
|
static const char_t stat_41[] = "Expected command letter";
|
|
static const char_t stat_42[] = "Bad number format";
|
|
static const char_t stat_43[] = "Input exceeds max length";
|
|
static const char_t stat_44[] = "Input value too small";
|
|
static const char_t stat_45[] = "Input value too large";
|
|
static const char_t stat_46[] = "Input value range error";
|
|
static const char_t stat_47[] = "Input value unsupported";
|
|
static const char_t stat_48[] = "JSON syntax error";
|
|
static const char_t stat_49[] = "JSON input has too many pairs";
|
|
static const char_t stat_50[] = "JSON output too long";
|
|
static const char_t stat_51[] = "Out of buffer space";
|
|
static const char_t stat_52[] = "52";
|
|
static const char_t stat_53[] = "53";
|
|
static const char_t stat_54[] = "54";
|
|
static const char_t stat_55[] = "55";
|
|
static const char_t stat_56[] = "56";
|
|
static const char_t stat_57[] = "57";
|
|
static const char_t stat_58[] = "58";
|
|
static const char_t stat_59[] = "59";
|
|
|
|
static const char_t stat_60[] = "Zero length move";
|
|
static const char_t stat_61[] = "Gcode block skipped";
|
|
static const char_t stat_62[] = "Gcode input error";
|
|
static const char_t stat_63[] = "Gcode feedrate error";
|
|
static const char_t stat_64[] = "Gcode axis word missing";
|
|
static const char_t stat_65[] = "Gcode modal group violation";
|
|
static const char_t stat_66[] = "Homing cycle failed";
|
|
static const char_t stat_67[] = "Max travel exceeded";
|
|
static const char_t stat_68[] = "Max spindle speed exceeded";
|
|
static const char_t stat_69[] = "Arc specification error";
|
|
|
|
static const char_t *stat_msg[] = {
|
|
stat_00, stat_01, stat_02, stat_03, stat_04, stat_05, stat_06, stat_07, stat_08, stat_09,
|
|
stat_10, stat_11, stat_12, stat_13, stat_14, stat_15, stat_16, stat_17, stat_18, stat_19,
|
|
stat_20, stat_21, stat_22, stat_23, stat_24, stat_25, stat_26, stat_27, stat_28, stat_29,
|
|
stat_30, stat_31, stat_32, stat_33, stat_34, stat_35, stat_36, stat_37, stat_38, stat_39,
|
|
stat_40, stat_41, stat_42, stat_43, stat_44, stat_45, stat_46, stat_47, stat_48, stat_49,
|
|
stat_50, stat_51, stat_52, stat_53, stat_54, stat_55, stat_56, stat_57, stat_58, stat_59,
|
|
stat_60, stat_61, stat_62, stat_63, stat_64, stat_65, stat_66, stat_67, stat_68, stat_69
|
|
};
|
|
|
|
const char_t *get_status_message(stat_t status)
|
|
{
|
|
return (stat_msg[status]);
|
|
}
|
|
|
|
/*
|
|
* rpt_exception() - generate an exception message
|
|
*/
|
|
void rpt_exception(uint8_t status, int16_t value)
|
|
{
|
|
printf("{\"er\":{\"fb\":%0.2f,\"st\":%d,\"msg\":\"%s\",\"val\":%d}\n",
|
|
TINYG2_FIRMWARE_BUILD, status, stat_msg[status], value);
|
|
}
|
|
|
|
/**** Application Messages *********************************************************
|
|
* rpt_print_message() - print a character string passed as argument
|
|
* 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(uint8_t status, const char *msg)
|
|
{
|
|
#ifndef __SUPPRESS_STARTUP_MESSAGES
|
|
cmd_reset_list();
|
|
cmd_add_object((const char_t *)"fb");
|
|
cmd_add_object((const char_t *)"fv");
|
|
cmd_add_object((const char_t *)"hv");
|
|
cmd_add_object((const char_t *)"id");
|
|
cmd_add_string((const char_t *)"msg", (char_t *)msg);
|
|
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)
|
|
{
|
|
_startup_helper(STAT_OK, "SYSTEM READY");
|
|
if (cfg.comm_mode == TEXT_MODE) { text_response(STAT_OK, (char_t *)"");}// 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
|
|
*/
|
|
|
|
/*
|
|
* rpt_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 rpt_init_status_report()
|
|
{
|
|
const char_t nul[] = "";
|
|
const char_t se00[] = "se00";
|
|
char_t sr_defaults[CMD_STATUS_REPORT_LEN][CMD_TOKEN_LEN+1] = { SR_DEFAULTS }; // see settings.h
|
|
cmdObj_t *cmd = cmd_reset_list(); // used for status report persistence locations
|
|
cs.status_report_counter = (cfg.status_report_interval / RTC_PERIOD); // RTC fires every 10 ms
|
|
|
|
cmd->index = cmd_get_index(nul, se00); // set first SR persistence index
|
|
for (uint8_t i=0; i < CMD_STATUS_REPORT_LEN ; i++) {
|
|
if (sr_defaults[i][0] == NUL) break; // quit on first blank array entry
|
|
cfg.status_report_value[i] = -1234567; // pre-load values with an unlikely number
|
|
cmd->value = cmd_get_index(nul, sr_defaults[i]);// load the index for the SR element
|
|
cmd_set(cmd);
|
|
cmd_persist(cmd); // conditionally persist - automatic by cmd_persis()
|
|
cmd->index++; // increment SR NVM index
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rpt_set_status_report() - interpret an sr setup string and return current report
|
|
*/
|
|
|
|
stat_t rpt_set_status_report(cmdObj_t *cmd)
|
|
{
|
|
uint8_t elements = 0;
|
|
index_t status_report_list[CMD_STATUS_REPORT_LEN];
|
|
memset(status_report_list, 0, sizeof(status_report_list));
|
|
index_t sr_start = cmd_get_index((const char_t *)"",(const char_t *)"se00");// set first SR persistence index
|
|
|
|
for (uint8_t i=0; i<CMD_STATUS_REPORT_LEN; i++) {
|
|
if (((cmd = cmd->nx) == NULL) || (cmd->type == TYPE_EMPTY)) { break;}
|
|
if ((cmd->type == TYPE_BOOL) && (fp_TRUE(cmd->value))) {
|
|
status_report_list[i] = cmd->index;
|
|
cmd->value = cmd->index; // persist the index as the value
|
|
cmd->index = sr_start + i; // index of the SR persistence location
|
|
cmd_persist(cmd);
|
|
elements++;
|
|
} else {
|
|
return (STAT_INPUT_VALUE_UNSUPPORTED);
|
|
}
|
|
}
|
|
if (elements == 0) { return (STAT_INPUT_VALUE_UNSUPPORTED);}
|
|
memcpy(cfg.status_report_list, status_report_list, sizeof(status_report_list));
|
|
rpt_populate_unfiltered_status_report(); // return current values
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* rpt_run_text_status_report() - generate a text mode status report in multiline format
|
|
* rpt_request_status_report() - request a status report to run after minimum interval
|
|
* rpt_force_status_report() - request a status report to run at the next main loop opporunity
|
|
* rpt_status_report_rtc_callback() - real-time clock downcount for minimum reporting interval
|
|
* rpt_status_report_callback() - main loop callback to send a report if one is ready
|
|
*
|
|
* 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
|
|
*/
|
|
void rpt_run_text_status_report()
|
|
{
|
|
rpt_populate_unfiltered_status_report();
|
|
cmd_print_list(STAT_OK, TEXT_MULTILINE_FORMATTED, JSON_RESPONSE_FORMAT);
|
|
}
|
|
|
|
void rpt_request_status_report(uint8_t request_type)
|
|
{
|
|
cs.status_report_request = request_type;
|
|
}
|
|
|
|
void rpt_status_report_rtc_callback() // called by 10ms real-time clock
|
|
{
|
|
if (--cs.status_report_counter == 0) {
|
|
cs.status_report_request = SR_IMMEDIATE_REQUEST; // promote to immediate request
|
|
cs.status_report_counter = (cfg.status_report_interval / RTC_PERIOD); // reset minimum interval
|
|
}
|
|
}
|
|
|
|
uint8_t rpt_status_report_callback() // called by controller dispatcher
|
|
{
|
|
if ((cfg.status_report_verbosity == SR_OFF) || (cs.status_report_request != SR_IMMEDIATE_REQUEST)) {
|
|
return (STAT_NOOP);
|
|
}
|
|
if (cfg.status_report_verbosity == SR_FILTERED) {
|
|
if (rpt_populate_filtered_status_report() == true) {
|
|
cmd_print_list(STAT_OK, TEXT_INLINE_PAIRS, JSON_OBJECT_FORMAT);
|
|
}
|
|
} else {
|
|
rpt_populate_unfiltered_status_report();
|
|
cmd_print_list(STAT_OK, TEXT_INLINE_PAIRS, JSON_OBJECT_FORMAT);
|
|
}
|
|
cs.status_report_request = SR_NO_REQUEST;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* rpt_populate_unfiltered_status_report() - populate cmdObj body with status values
|
|
*
|
|
* Designed to be run as a response; i.e. have a "r" header and a footer.
|
|
*/
|
|
|
|
void rpt_populate_unfiltered_status_report()
|
|
{
|
|
const char_t nul[] = "";
|
|
const char_t sr[] = "sr";
|
|
char_t tmp[CMD_TOKEN_LEN+1];
|
|
cmdObj_t *cmd = cmd_reset_list(); // sets *cmd to the start of the body
|
|
cmd->type = TYPE_PARENT; // setup the parent object
|
|
strcpy(cmd->token, sr);
|
|
// sprintf_P(cmd->token, PSTR("sr")); // alternate form of above: less RAM, more FLASH & cycles
|
|
cmd->index = cmd_get_index(nul, sr); // set the index - may be needed by calling function
|
|
cmd = cmd->nx;
|
|
|
|
for (uint8_t i=0; i<CMD_STATUS_REPORT_LEN; i++) {
|
|
if ((cmd->index = cfg.status_report_list[i]) == 0) { break;}
|
|
cmd_get_cmdObj(cmd);
|
|
strcpy(tmp, cmd->group); // concatenate groups and tokens
|
|
strcat(tmp, cmd->token);
|
|
strcpy(cmd->token, tmp);
|
|
cmd = cmd->nx;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rpt_populate_filtered_status_report() - populate cmdObj 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 rpt_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)
|
|
*/
|
|
|
|
uint8_t rpt_populate_filtered_status_report()
|
|
{
|
|
const char_t sr[] = "sr";
|
|
char_t tmp[CMD_TOKEN_LEN+1];
|
|
uint8_t has_data = false;
|
|
cmdObj_t *cmd = cmd_reset_list(); // sets cmd to the start of the body
|
|
|
|
cmd->type = TYPE_PARENT; // setup the parent object
|
|
strcpy(cmd->token, sr);
|
|
// sprintf_P(cmd->token, PSTR("sr")); // alternate form of above: less RAM, more FLASH & cycles
|
|
// cmd->index = cmd_get_index(nul, sr); // OMITTED - set the index - may be needed by calling function
|
|
cmd = cmd->nx;
|
|
|
|
for (uint8_t i=0; i<CMD_STATUS_REPORT_LEN; i++) {
|
|
if ((cmd->index = cfg.status_report_list[i]) == 0) { break;}
|
|
|
|
cmd_get_cmdObj(cmd);
|
|
// if (cfg.status_report_value[i] == cmd->value) { // float == comparison runs the risk of overreporting. So be it
|
|
if (fp_EQ(cmd->value, cfg.status_report_value[i])) {
|
|
continue;
|
|
} else {
|
|
strcpy(tmp, cmd->group); // flatten out groups
|
|
strcat(tmp, cmd->token);
|
|
strcpy(cmd->token, tmp);
|
|
cfg.status_report_value[i] = cmd->value;
|
|
cmd = cmd->nx;
|
|
// if (cmd == NULL) { return (false);} // This is never supposed to happen
|
|
has_data = true;
|
|
}
|
|
}
|
|
cmd->pv->nx = NULL; // back up one and terminate the body
|
|
return (has_data);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Queue Reports
|
|
* rpt_request_queue_report() - request a queue report with current values
|
|
* rpt_queue_report_callback() - run the queue report w/stored values
|
|
*/
|
|
|
|
struct qrIndexes { // static data for queue reports
|
|
uint8_t request; // set to true to request a report
|
|
uint8_t buffers_available; // stored value used by callback
|
|
uint8_t prev_available; // used to filter reports
|
|
};
|
|
static struct qrIndexes qr;
|
|
|
|
void rpt_request_queue_report()
|
|
{
|
|
if (cfg.queue_report_verbosity == QR_OFF) return;
|
|
|
|
qr.buffers_available = mp_get_planner_buffers_available();
|
|
|
|
// perform filtration for QR_FILTERED reports
|
|
if (cfg.queue_report_verbosity == QR_FILTERED) {
|
|
if (qr.buffers_available == qr.prev_available) {
|
|
return;
|
|
}
|
|
if ((qr.buffers_available > cfg.queue_report_lo_water) && // e.g. > 2 buffers available
|
|
(qr.buffers_available < cfg.queue_report_hi_water)) { // e.g. < 20 buffers available
|
|
return;
|
|
}
|
|
}
|
|
qr.prev_available = qr.buffers_available;
|
|
qr.request = true;
|
|
}
|
|
|
|
stat_t rpt_queue_report_callback()
|
|
{
|
|
if (qr.request == false) { return (STAT_NOOP);}
|
|
qr.request = false;
|
|
|
|
// cget a clean cmd object
|
|
// cmdObj_t *cmd = cmd_reset_list(); // normally you do a list reset but the following is more time efficient
|
|
cmdObj_t *cmd = cmd_body;
|
|
cmd_reset_obj(cmd);
|
|
cmd->nx = NULL; // terminate the list
|
|
|
|
// make a qr object and print it
|
|
sprintf((char *)cmd->token, "qr");
|
|
cmd->value = qr.buffers_available;
|
|
cmd->type = TYPE_INTEGER;
|
|
cmd_print_list(STAT_OK, TEXT_INLINE_PAIRS, JSON_OBJECT_FORMAT);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/****************************************************************************
|
|
***** Report Unit Tests ****************************************************
|
|
****************************************************************************/
|
|
|
|
#ifdef __UNIT_TESTS
|
|
#ifdef __UNIT_TEST_REPORT
|
|
|
|
void sr_unit_tests(void)
|
|
{
|
|
sr_init();
|
|
tg.communications_mode = STAT_JSON_MODE;
|
|
sr_run_status_report();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|