Files
grblHAL/report.c
2021-09-09 19:19:09 +02:00

2061 lines
64 KiB
C

/*
report.c - reporting and messaging methods
Part of grblHAL
Copyright (c) 2017-2021 Terje Io
Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
/*
This file functions as the primary feedback interface for Grbl. Any outgoing data, such
as the protocol status messages, feedback messages, and status reports, are stored here.
For the most part, these functions primarily are called from protocol.c methods. If a
different style feedback is desired (i.e. JSON), then a user can change these following
methods to accomodate their needs.
*/
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "hal.h"
#include "report.h"
#include "nvs_buffer.h"
#include "limits.h"
#include "state_machine.h"
#ifdef ENABLE_SPINDLE_LINEARIZATION
#include <stdio.h>
#endif
#ifndef REPORT_OVERRIDE_REFRESH_BUSY_COUNT
#define REPORT_OVERRIDE_REFRESH_BUSY_COUNT 20 // (1-255)
#endif
#ifndef REPORT_OVERRIDE_REFRESH_IDLE_COUNT
#define REPORT_OVERRIDE_REFRESH_IDLE_COUNT 10 // (1-255) Must be less than or equal to the busy count
#endif
#ifndef REPORT_WCO_REFRESH_BUSY_COUNT
#define REPORT_WCO_REFRESH_BUSY_COUNT 30 // (2-255)
#endif
#ifndef REPORT_WCO_REFRESH_IDLE_COUNT
#define REPORT_WCO_REFRESH_IDLE_COUNT 10 // (2-255) Must be less than or equal to the busy count
#endif
// Compile-time sanity check of defines
#if (REPORT_WCO_REFRESH_BUSY_COUNT < REPORT_WCO_REFRESH_IDLE_COUNT)
#error "WCO busy refresh is less than idle refresh."
#endif
#if (REPORT_OVERRIDE_REFRESH_BUSY_COUNT < REPORT_OVERRIDE_REFRESH_IDLE_COUNT)
#error "Override busy refresh is less than idle refresh."
#endif
#if (REPORT_WCO_REFRESH_IDLE_COUNT < 2)
#error "WCO refresh must be greater than one."
#endif
#if (REPORT_OVERRIDE_REFRESH_IDLE_COUNT < 1)
#error "Override refresh must be greater than zero."
#endif
static char buf[(STRLEN_COORDVALUE + 1) * N_AXIS];
static char *(*get_axis_values)(float *axis_values);
static char *(*get_axis_value)(float value);
static char *(*get_rate_value)(float value);
static uint8_t override_counter = 0; // Tracks when to add override data to status reports.
static uint8_t wco_counter = 0; // Tracks when to add work coordinate offset data to status reports.
static const char vbar[2] = { '|', '\0' };
static bool report_setting (const setting_detail_t *setting, uint_fast16_t offset, void *data);
static const report_t report_fns = {
.setting = report_setting,
.status_message = report_status_message,
.feedback_message = report_feedback_message
};
// Append a number of strings to the static buffer
// NOTE: do NOT use for several int/float conversions as these share the same underlying buffer!
static char *appendbuf (int argc, ...)
{
char c, *s = buf, *arg;
va_list list;
va_start(list, argc);
while(argc--) {
arg = va_arg(list, char *);
do {
c = *s++ = *arg++;
} while(c);
s--;
}
va_end(list);
return buf;
}
static char *map_coord_system (coord_system_id_t id)
{
uint8_t g5x = id + 54;
strcpy(buf, uitoa((uint32_t)(g5x > 59 ? 59 : g5x)));
if(g5x > 59) {
strcat(buf, ".");
strcat(buf, uitoa((uint32_t)(g5x - 59)));
}
return buf;
}
// Convert axis position values to null terminated string (mm).
static char *get_axis_values_mm (float *axis_values)
{
uint_fast32_t idx;
buf[0] = '\0';
for (idx = 0; idx < N_AXIS; idx++) {
if(idx == X_AXIS && gc_state.modal.diameter_mode)
strcat(buf, ftoa(axis_values[idx] * 2.0f, N_DECIMAL_COORDVALUE_MM));
else
strcat(buf, ftoa(axis_values[idx], N_DECIMAL_COORDVALUE_MM));
if (idx < (N_AXIS - 1))
strcat(buf, ",");
}
return buf;
}
// Convert axis position values to null terminated string (inch).
static char *get_axis_values_inches (float *axis_values)
{
uint_fast32_t idx;
buf[0] = '\0';
for (idx = 0; idx < N_AXIS; idx++) {
if(idx == X_AXIS && gc_state.modal.diameter_mode)
strcat(buf, ftoa(axis_values[idx] * INCH_PER_MM * 2.0f, N_DECIMAL_COORDVALUE_INCH));
else
strcat(buf, ftoa(axis_values[idx] * INCH_PER_MM, N_DECIMAL_COORDVALUE_INCH));
if (idx < (N_AXIS - 1))
strcat(buf, ",");
}
return buf;
}
// Convert rate value to null terminated string (mm).
static char *get_axis_value_mm (float value)
{
return strcpy(buf, ftoa(value, N_DECIMAL_COORDVALUE_MM));
}
// Convert rate value to null terminated string (mm).
static char *get_axis_value_inches (float value)
{
return strcpy(buf, ftoa(value * INCH_PER_MM, N_DECIMAL_COORDVALUE_INCH));
}
// Convert rate value to null terminated string (mm).
static char *get_rate_value_mm (float value)
{
return uitoa((uint32_t)value);
}
// Convert rate value to null terminated string (mm).
static char *get_rate_value_inch (float value)
{
return uitoa((uint32_t)(value * INCH_PER_MM));
}
// Convert axes signals bits to string representation.
// NOTE: returns pointer to null terminator!
inline static char *axis_signals_tostring (char *buf, axes_signals_t signals)
{
if(signals.x)
*buf++ = 'X';
if(signals.y)
*buf++ = 'Y';
if (signals.z)
*buf++ = 'Z';
#ifdef A_AXIS
if (signals.a)
*buf++ = 'A';
#endif
#ifdef B_AXIS
if (signals.b)
*buf++ = 'B';
#endif
#ifdef C_AXIS
if (signals.c)
*buf++ = 'C';
#endif
*buf = '\0';
return buf;
}
// Convert control signals bits to string representation.
// NOTE: returns pointer to null terminator!
inline static char *control_signals_tostring (char *buf, control_signals_t signals)
{
if (signals.safety_door_ajar && hal.signals_cap.safety_door_ajar)
*buf++ = 'D';
if (signals.reset)
*buf++ = 'R';
if (signals.feed_hold)
*buf++ = 'H';
if (signals.cycle_start)
*buf++ = 'S';
if (signals.e_stop)
*buf++ = 'E';
if (signals.block_delete && sys.flags.block_delete_enabled)
*buf++ = 'L';
if (hal.signals_cap.stop_disable ? signals.stop_disable : sys.flags.optional_stop_disable)
*buf++ = 'T';
if (signals.motor_warning)
*buf++ = 'W';
if (signals.motor_fault)
*buf++ = 'M';
*buf = '\0';
return buf;
}
void report_init (void)
{
get_axis_value = settings.flags.report_inches ? get_axis_value_inches : get_axis_value_mm;
get_axis_values = settings.flags.report_inches ? get_axis_values_inches : get_axis_values_mm;
get_rate_value = settings.flags.report_inches ? get_rate_value_inch : get_rate_value_mm;
}
void report_init_fns (void)
{
memcpy(&grbl.report, &report_fns, sizeof(report_t));
}
// Handles the primary confirmation protocol response for streaming interfaces and human-feedback.
// For every incoming line, this method responds with an 'ok' for a successful command or an
// 'error:' to indicate some error event with the line or some critical system error during
// operation. Errors events can originate from the g-code parser, settings module, or asynchronously
// from a critical error, such as a triggered hard limit. Interface should always monitor for these
// responses.
status_code_t report_status_message (status_code_t status_code)
{
switch(status_code) {
case Status_OK: // STATUS_OK
hal.stream.write("ok" ASCII_EOL);
break;
default:
hal.stream.write(appendbuf(3, "error:", uitoa((uint32_t)status_code), ASCII_EOL));
break;
}
return status_code;
}
// Prints alarm messages.
alarm_code_t report_alarm_message (alarm_code_t alarm_code)
{
hal.stream.write_all(appendbuf(3, "ALARM:", uitoa((uint32_t)alarm_code), ASCII_EOL));
hal.delay_ms(500, NULL); // Force delay to ensure message clears output stream buffer.
return alarm_code;
}
// Prints feedback message, typically from gcode.
void report_message (const char *msg, message_type_t type)
{
hal.stream.write("[MSG:");
switch(type) {
case Message_Info:
hal.stream.write("Info: ");
break;
case Message_Warning:
hal.stream.write("Warning: ");
break;
default:
break;
}
hal.stream.write(msg);
hal.stream.write("]" ASCII_EOL);
}
// Prints feedback messages. This serves as a centralized method to provide additional
// user feedback for things that are not of the status/alarm message protocol. These are
// messages such as setup warnings, switch toggling, and how to exit alarms.
// NOTE: For interfaces, messages are always placed within brackets. And if silent mode
// is installed, the message number codes are less than zero.
message_code_t report_feedback_message (message_code_t id)
{
const char *msg = NULL;
uint_fast16_t idx = 0;
hal.stream.write_all("[MSG:");
do {
if(messages[idx].id == id)
msg = messages[idx].msg;
} while(msg == NULL && ++idx < Message_NextMessage);
hal.stream.write_all(msg ? msg : "");
hal.stream.write_all("]" ASCII_EOL);
return id;
}
// Welcome message
void report_init_message (void)
{
override_counter = wco_counter = 0;
#if COMPATIBILITY_LEVEL == 0
hal.stream.write_all(ASCII_EOL "GrblHAL " GRBL_VERSION " ['$' or '$HELP' for help]" ASCII_EOL);
#else
hal.stream.write_all(ASCII_EOL "Grbl " GRBL_VERSION " ['$' for help]" ASCII_EOL);
#endif
}
// Grbl help message
void report_grbl_help (void)
{
hal.stream.write("[HLP:$$ $# $G $I $N $x=val $Nx=line $J=line $SLP $C $X $H $B ~ ! ? ctrl-x]" ASCII_EOL);
}
#define CAPS(c) ((c >= 'a' && c <= 'z') ? c & 0x5F : c)
static void report_group_settings (const setting_group_detail_t *groups, const uint_fast8_t n_groups, char *lcargs)
{
uint_fast8_t idx;
uint_fast8_t len = strlen(lcargs);
for(idx = 0; idx < n_groups; idx++) {
if(strlen(groups[idx].name) == len) {
char *s1 = lcargs, *s2 = (char *)groups[idx].name;
while(*s1 && CAPS(*s1) == CAPS(*s2)) {
s1++;
s2++;
}
if(*s1 == '\0') {
report_settings_details(SettingsFormat_HumanReadable, Setting_SettingsAll, groups[idx].id);
break;
}
}
}
}
status_code_t report_help (char *args, char *lcargs)
{
setting_details_t *settings_info = settings_get_details();
// Strip leading spaces
while(*args == ' ')
args++;
if(*args == '\0') {
hal.stream.write("Help arguments:" ASCII_EOL);
hal.stream.write(" Commands" ASCII_EOL);
hal.stream.write(" Settings" ASCII_EOL);
report_setting_group_details(false, " ");
} else {
if(!strncmp(args, "COMMANDS", 8)) {
hal.stream.write("$I - list system information" ASCII_EOL);
hal.stream.write("$$ - list settings" ASCII_EOL);
hal.stream.write("$# - list offsets, tool table, probing and home position" ASCII_EOL);
hal.stream.write("$G - list parser state" ASCII_EOL);
hal.stream.write("$N - list startup lines" ASCII_EOL);
if(settings.homing.flags.enabled)
hal.stream.write("$H - home configured axes" ASCII_EOL);
if(settings.homing.flags.single_axis_commands)
hal.stream.write("$H<axisletter> - home single axis" ASCII_EOL);
hal.stream.write("$X - unlock machine" ASCII_EOL);
hal.stream.write("$SLP - enter sleep mode" ASCII_EOL);
hal.stream.write("$HELP <arg> - help" ASCII_EOL);
hal.stream.write("$RST=* - restore/reset all" ASCII_EOL);
hal.stream.write("$RST=$ - restore default settings" ASCII_EOL);
if(settings_info->on_get_settings)
hal.stream.write("$RST=& - restore driver and plugin default settings" ASCII_EOL);
#ifdef N_TOOLS
hal.stream.write("$RST=# - reset offsets and tool data" ASCII_EOL);
#else
hal.stream.write("$RST=# - reset offsets" ASCII_EOL);
#endif
if(grbl.on_report_command_help)
grbl.on_report_command_help();
} else if(!strncmp(args, "SETTINGS", 8))
report_settings_details(SettingsFormat_HumanReadable, Setting_SettingsAll, Group_All);
else {
// Strip leading spaces from lowercase version
while(*lcargs == ' ')
lcargs++;
report_group_settings(settings_info->groups, settings_info->n_groups, lcargs);
if(grbl.on_get_settings) {
on_get_settings_ptr on_get_settings = grbl.on_get_settings;
while(on_get_settings) {
settings_info = on_get_settings();
if(settings_info->groups)
report_group_settings(settings_info->groups, settings_info->n_groups, lcargs);
on_get_settings = settings_info->on_get_settings;
}
}
}
}
return Status_OK;
}
// Grbl settings print out.
static int cmp_settings (const void *a, const void *b)
{
return (*(setting_detail_t **)(a))->id - (*(setting_detail_t **)(b))->id;
}
static bool report_setting (const setting_detail_t *setting, uint_fast16_t offset, void *data)
{
appendbuf(3, "$", uitoa(setting->id + offset), "=");
char *value = setting_get_value(setting, offset);
if(value) {
hal.stream.write(buf);
hal.stream.write(value);
hal.stream.write(ASCII_EOL);
}
return true;
}
status_code_t report_grbl_setting (setting_id_t id, void *data)
{
status_code_t status = Status_OK;
const setting_detail_t *setting = setting_get_details(id, NULL);
if(setting)
grbl.report.setting(setting, id - setting->id, data);
else
status = Status_SettingDisabled;
return status;
}
static bool print_setting (const setting_detail_t *setting, uint_fast16_t offset, void *data)
{
if(setting->value != NULL)
grbl.report.setting(setting, offset, data);
else {
hal.stream.write("$");
hal.stream.write(uitoa(setting->id));
hal.stream.write("=N/A" ASCII_EOL);
}
return true;
}
void report_grbl_settings (bool all, void *data)
{
setting_details_t *details = settings_get_details();
uint16_t n_settings = details->n_settings;
const setting_detail_t *setting;
while(details->on_get_settings) {
details = details->on_get_settings();
n_settings += details->n_settings;
}
setting_detail_t **all_settings, **psetting;
if((all_settings = calloc(n_settings, sizeof(setting_detail_t *)))) {
uint_fast16_t idx;
details = settings_get_details();
psetting = all_settings;
n_settings = 0;
for(idx = 0; idx < details->n_settings; idx++) {
setting = &details->settings[idx];
if((all || setting->type == Setting_IsLegacy || setting->type == Setting_IsLegacyFn) &&
(setting->is_available == NULL ||setting->is_available(setting))) {
*psetting++ = (setting_detail_t *)setting;
n_settings++;
}
}
if(all) while(details->on_get_settings) {
details = details->on_get_settings();
for(idx = 0; idx < details->n_settings; idx++) {
setting = &details->settings[idx];
if(setting->is_available == NULL ||setting->is_available(setting)) {
*psetting++ = (setting_detail_t *)setting;
n_settings++;
}
}
}
qsort(all_settings, n_settings, sizeof(setting_detail_t *), cmp_settings);
for(idx = 0; idx < n_settings; idx++)
settings_iterator(all_settings[idx], print_setting, data);
free(all_settings);
}
}
// Prints current probe parameters. Upon a probe command, these parameters are updated upon a
// successful probe or upon a failed probe with the G38.3 without errors command (if supported).
// These values are retained until Grbl is power-cycled, whereby they will be re-zeroed.
void report_probe_parameters (void)
{
// Report in terms of machine position.
float print_position[N_AXIS];
system_convert_array_steps_to_mpos(print_position, sys.probe_position);
hal.stream.write("[PRB:");
hal.stream.write(get_axis_values(print_position));
hal.stream.write(sys.flags.probe_succeeded ? ":1" : ":0");
hal.stream.write("]" ASCII_EOL);
}
// Prints current home position in terms of machine position.
// Bitmask for homed axes attached.
void report_home_position (void)
{
hal.stream.write("[HOME:");
hal.stream.write(get_axis_values(sys.home_position));
hal.stream.write(":");
hal.stream.write(uitoa(sys.homed.mask));
hal.stream.write("]" ASCII_EOL);
}
// Prints current tool offsets.
void report_tool_offsets (void)
{
hal.stream.write("[TLO:");
#ifdef TOOL_LENGTH_OFFSET_AXIS
hal.stream.write(get_axis_value(gc_state.tool_length_offset[Z_AXIS]));
#else
hal.stream.write(get_axis_values(gc_state.tool_length_offset));
#endif
hal.stream.write("]" ASCII_EOL);
}
// Prints Grbl NGC parameters (coordinate offsets, probing, tool table)
void report_ngc_parameters (void)
{
uint_fast8_t idx;
float coord_data[N_AXIS];
if(gc_state.modal.scaling_active) {
hal.stream.write("[G51:");
hal.stream.write(get_axis_values(gc_get_scaling()));
hal.stream.write("]" ASCII_EOL);
}
for (idx = 0; idx < N_CoordinateSystems; idx++) {
if (!(settings_read_coord_data((coord_system_id_t)idx, &coord_data))) {
grbl.report.status_message(Status_SettingReadFail);
return;
}
hal.stream.write("[G");
switch (idx) {
case CoordinateSystem_G28:
hal.stream.write("28");
break;
case CoordinateSystem_G30:
hal.stream.write("30");
break;
case CoordinateSystem_G92:
break;
default: // G54-G59
hal.stream.write(map_coord_system((coord_system_id_t)idx));
break;
}
if(idx != CoordinateSystem_G92) {
hal.stream.write(":");
hal.stream.write(get_axis_values(coord_data));
hal.stream.write("]" ASCII_EOL);
}
}
// Print G92, G92.1 which are not persistent in memory
hal.stream.write("92:");
hal.stream.write(get_axis_values(gc_state.g92_coord_offset));
hal.stream.write("]" ASCII_EOL);
#ifdef N_TOOLS
for (idx = 1; idx <= N_TOOLS; idx++) {
hal.stream.write("[T:");
hal.stream.write(uitoa((uint32_t)idx));
hal.stream.write("|");
hal.stream.write(get_axis_values(tool_table[idx].offset));
hal.stream.write("|");
hal.stream.write(get_axis_value(tool_table[idx].radius));
hal.stream.write("]" ASCII_EOL);
}
#endif
#if COMPATIBILITY_LEVEL < 10
if(settings.homing.flags.enabled)
report_home_position();
#endif
report_tool_offsets(); // Print tool length offset value.
report_probe_parameters(); // Print probe parameters. Not persistent in memory.
if(sys.tlo_reference_set.mask) { // Print tool length reference offset. Not persistent in memory.
plane_t plane;
gc_get_plane_data(&plane, gc_state.modal.plane_select);
hal.stream.write("[TLR:");
hal.stream.write(get_axis_value(sys.tlo_reference[plane.axis_linear] / settings.axis[plane.axis_linear].steps_per_mm));
hal.stream.write("]" ASCII_EOL);
}
}
static inline bool is_g92_active (void)
{
bool active = false;
uint_fast32_t idx = N_AXIS;
do {
idx--;
active = !(gc_state.g92_coord_offset[idx] == 0.0f || gc_state.g92_coord_offset[idx] == -0.0f);
} while(idx && !active);
return active;
}
// Print current gcode parser mode state
void report_gcode_modes (void)
{
hal.stream.write("[GC:G");
if (gc_state.modal.motion >= MotionMode_ProbeToward) {
hal.stream.write("38.");
hal.stream.write(uitoa((uint32_t)(gc_state.modal.motion - (MotionMode_ProbeToward - 2))));
} else
hal.stream.write(uitoa((uint32_t)gc_state.modal.motion));
hal.stream.write(" G");
hal.stream.write(map_coord_system(gc_state.modal.coord_system.id));
#if COMPATIBILITY_LEVEL < 10
if(is_g92_active())
hal.stream.write(" G92");
#endif
if(settings.mode == Mode_Lathe)
hal.stream.write(gc_state.modal.diameter_mode ? " G7" : " G8");
hal.stream.write(" G");
hal.stream.write(uitoa((uint32_t)(gc_state.modal.plane_select + 17)));
hal.stream.write(gc_state.modal.units_imperial ? " G20" : " G21");
hal.stream.write(gc_state.modal.distance_incremental ? " G91" : " G90");
hal.stream.write(" G");
hal.stream.write(uitoa((uint32_t)(94 - gc_state.modal.feed_mode)));
if(settings.mode == Mode_Lathe && hal.driver_cap.variable_spindle)
hal.stream.write(gc_state.modal.spindle_rpm_mode == SpindleSpeedMode_RPM ? " G97" : " G96");
#if COMPATIBILITY_LEVEL < 10
if(gc_state.modal.tool_offset_mode == ToolLengthOffset_Cancel)
hal.stream.write(" G49");
else {
hal.stream.write(" G43");
if(gc_state.modal.tool_offset_mode != ToolLengthOffset_Enable)
hal.stream.write(gc_state.modal.tool_offset_mode == ToolLengthOffset_EnableDynamic ? ".1" : ".2");
}
hal.stream.write(gc_state.canned.retract_mode == CCRetractMode_RPos ? " G99" : " G98");
if(gc_state.modal.scaling_active) {
hal.stream.write(" G51:");
axis_signals_tostring(buf, gc_get_g51_state());
hal.stream.write(buf);
} else
hal.stream.write(" G50");
#endif
if (gc_state.modal.program_flow) {
switch (gc_state.modal.program_flow) {
case ProgramFlow_Paused:
hal.stream.write(" M0");
break;
case ProgramFlow_OptionalStop:
hal.stream.write(" M1");
break;
case ProgramFlow_CompletedM2:
hal.stream.write(" M2");
break;
case ProgramFlow_CompletedM30:
hal.stream.write(" M30");
break;
case ProgramFlow_CompletedM60:
hal.stream.write(" M60");
break;
default:
break;
}
}
hal.stream.write(gc_state.modal.spindle.on ? (gc_state.modal.spindle.ccw ? " M4" : " M3") : " M5");
if(gc_state.tool_change)
hal.stream.write(" M6");
if (gc_state.modal.coolant.value) {
if (gc_state.modal.coolant.mist)
hal.stream.write(" M7");
if (gc_state.modal.coolant.flood)
hal.stream.write(" M8");
} else
hal.stream.write(" M9");
if (sys.override.control.feed_rate_disable)
hal.stream.write(" M50");
if (sys.override.control.spindle_rpm_disable)
hal.stream.write(" M51");
if (sys.override.control.feed_hold_disable)
hal.stream.write(" M53");
if (settings.parking.flags.enable_override_control && sys.override.control.parking_disable)
hal.stream.write(" M56");
hal.stream.write(appendbuf(2, " T", uitoa((uint32_t)gc_state.tool->tool)));
hal.stream.write(appendbuf(2, " F", get_rate_value(gc_state.feed_rate)));
if(hal.driver_cap.variable_spindle)
hal.stream.write(appendbuf(2, " S", ftoa(gc_state.spindle.rpm, N_DECIMAL_RPMVALUE)));
hal.stream.write("]" ASCII_EOL);
}
// Prints specified startup line
void report_startup_line (uint8_t n, char *line)
{
hal.stream.write(appendbuf(3, "$N", uitoa((uint32_t)n), "="));
hal.stream.write(line);
hal.stream.write(ASCII_EOL);
}
void report_execute_startup_message (char *line, status_code_t status_code)
{
hal.stream.write(">");
hal.stream.write(line);
hal.stream.write(":");
grbl.report.status_message(status_code);
}
// Prints build info line
void report_build_info (char *line, bool extended)
{
hal.stream.write("[VER:" GRBL_VERSION "." GRBL_VERSION_BUILD ":");
hal.stream.write(line);
hal.stream.write("]" ASCII_EOL);
#if COMPATIBILITY_LEVEL == 0
extended = true;
#endif
// Generate compile-time build option list
char *append = &buf[5];
strcpy(buf, "[OPT:");
if(hal.driver_cap.variable_spindle)
*append++ = 'V';
*append++ = 'N';
if(hal.driver_cap.mist_control)
*append++ = 'M';
#ifdef COREXY
*append++ = 'C';
#endif
if(settings.parking.flags.enabled)
*append++ = 'P';
if(settings.homing.flags.force_set_origin)
*append++ = 'Z';
if(settings.homing.flags.single_axis_commands)
*append++ = 'H';
if(settings.limits.flags.two_switches)
*append++ = 'T';
if(settings.probe.allow_feed_override)
*append++ = 'A';
if(settings.spindle.flags.pwm_action == SpindleAction_DisableWithZeroSPeed)
*append++ = '0';
if(hal.driver_cap.software_debounce)
*append++ = 'S';
if(settings.parking.flags.enable_override_control)
*append++ = 'R';
if(!settings.homing.flags.init_lock)
*append++ = 'L';
if(hal.signals_cap.safety_door_ajar)
*append++ = '+';
#ifdef DISABLE_RESTORE_NVS_WIPE_ALL // NOTE: Shown when disabled.
*append++ = '*';
#endif
#ifdef DISABLE_RESTORE_NVS_DEFAULT_SETTINGS // NOTE: Shown when disabled.
*append++ = '$';
#endif
#ifdef DISABLE_RESTORE_NVS_CLEAR_PARAMETERS // NOTE: Shown when disabled.
*append++ = '#';
#endif
#ifdef DISABLE_BUILD_INFO_WRITE_COMMAND // NOTE: Shown when disabled.
*append++ = 'I';
#endif
if(!settings.status_report.sync_on_wco_change) // NOTE: Shown when disabled.
*append++ = 'W';
if(hal.stepper.get_auto_squared)
*append++ = '2';
*append++ = ',';
*append = '\0';
hal.stream.write(buf);
// NOTE: Compiled values, like override increments/max/min values, may be added at some point later.
hal.stream.write(uitoa((uint32_t)(BLOCK_BUFFER_SIZE - 1)));
hal.stream.write(",");
hal.stream.write(uitoa(hal.rx_buffer_size));
if(extended) {
hal.stream.write(",");
hal.stream.write(uitoa((uint32_t)N_AXIS));
hal.stream.write(",");
#ifdef N_TOOLS
hal.stream.write(uitoa((uint32_t)N_TOOLS));
#else
hal.stream.write("0");
#endif
}
hal.stream.write("]" ASCII_EOL);
if(extended) {
nvs_io_t *nvs = nvs_buffer_get_physical();
strcpy(buf, "[NEWOPT:ENUMS,RT");
strcat(buf, settings.flags.legacy_rt_commands ? "+," : "-,");
if(settings.homing.flags.enabled)
strcat(buf, "HOME,");
if(!hal.probe.get_state)
strcat(buf, "NOPROBE,");
else if(hal.signals_cap.probe_disconnected)
strcat(buf, "PC,");
if(hal.signals_cap.stop_disable)
strcat(buf, "OS,");
if(hal.signals_cap.block_delete)
strcat(buf, "BD,");
if(hal.signals_cap.e_stop)
strcat(buf, "ES,");
if(hal.driver_cap.mpg_mode)
strcat(buf, "MPG,");
if(settings.mode == Mode_Lathe)
strcat(buf, "LATHE,");
#ifdef N_TOOLS
if(hal.driver_cap.atc && hal.tool.change)
strcat(buf, "ATC,");
else
#endif
if(hal.stream.suspend_read)
strcat(buf, "TC,"); // Manual tool change supported (M6)
if(hal.driver_cap.spindle_sync)
strcat(buf, "SS,");
#ifndef NO_SETTINGS_DESCRIPTIONS
strcat(buf, "SED,");
#endif
#ifdef PID_LOG
strcat(buf, "PID,");
#endif
append = &buf[strlen(buf) - 1];
if(*append == ',')
*append = '\0';
hal.stream.write(buf);
grbl.on_report_options(true);
hal.stream.write("]" ASCII_EOL);
hal.stream.write("[FIRMWARE:grblHAL]" ASCII_EOL);
if(!(nvs->type == NVS_None || nvs->type == NVS_Emulated)) {
hal.stream.write("[NVS STORAGE:");
*buf = '\0';
if(hal.nvs.type == NVS_Emulated)
strcat(buf, "*");
strcat(buf, nvs->type == NVS_Flash ? "FLASH" : (nvs->type == NVS_FRAM ? "FRAM" : "EEPROM"));
hal.stream.write(buf);
hal.stream.write("]" ASCII_EOL);
}
if(hal.info) {
hal.stream.write("[DRIVER:");
hal.stream.write(hal.info);
hal.stream.write("]" ASCII_EOL);
}
if(hal.driver_version) {
hal.stream.write("[DRIVER VERSION:");
hal.stream.write(hal.driver_version);
hal.stream.write("]" ASCII_EOL);
}
if(hal.driver_options) {
hal.stream.write("[DRIVER OPTIONS:");
hal.stream.write(hal.driver_options);
hal.stream.write("]" ASCII_EOL);
}
if(hal.board) {
hal.stream.write("[BOARD:");
hal.stream.write(hal.board);
hal.stream.write("]" ASCII_EOL);
}
if(hal.max_step_rate) {
hal.stream.write("[MAX STEP RATE:");
hal.stream.write(uitoa(hal.max_step_rate));
hal.stream.write(" Hz]" ASCII_EOL);
}
#if COMPATIBILITY_LEVEL > 0
hal.stream.write("[COMPATIBILITY LEVEL:");
hal.stream.write(uitoa(COMPATIBILITY_LEVEL));
hal.stream.write("]" ASCII_EOL);
#endif
if(hal.port.num_digital_in + hal.port.num_digital_out + hal.port.num_analog_in + hal.port.num_analog_out > 0) {
hal.stream.write("[AUX IO:");
hal.stream.write(uitoa(hal.port.num_digital_in));
hal.stream.write(",");
hal.stream.write(uitoa(hal.port.num_digital_out));
hal.stream.write(",");
hal.stream.write(uitoa(hal.port.num_analog_in));
hal.stream.write(",");
hal.stream.write(uitoa(hal.port.num_analog_out));
hal.stream.write("]" ASCII_EOL);
}
grbl.on_report_options(false);
}
}
// Prints the character string line Grbl has received from the user, which has been pre-parsed,
// and has been sent into protocol_execute_line() routine to be executed by Grbl.
void report_echo_line_received (char *line)
{
hal.stream.write("[echo: ");
hal.stream.write(line);
hal.stream.write("]" ASCII_EOL);
}
// Prints real-time data. This function grabs a real-time snapshot of the stepper subprogram
// and the actual location of the CNC machine. Users may change the following function to their
// specific needs, but the desired real-time data report must be as short as possible. This is
// requires as it minimizes the computational overhead and allows grbl to keep running smoothly,
// especially during g-code programs with fast, short line segments and high frequency reports (5-20Hz).
void report_realtime_status (void)
{
static bool probing = false;
int32_t current_position[N_AXIS]; // Copy current state of the system position variable
float print_position[N_AXIS];
probe_state_t probe_state = {
.connected = On,
.triggered = Off
};
memcpy(current_position, sys.position, sizeof(sys.position));
system_convert_array_steps_to_mpos(print_position, current_position);
if(hal.probe.get_state)
probe_state = hal.probe.get_state();
// Report current machine state and sub-states
hal.stream.write_all("<");
sys_state_t state = state_get();
switch (gc_state.tool_change && state == STATE_CYCLE ? STATE_TOOL_CHANGE : state) {
case STATE_IDLE:
hal.stream.write_all("Idle");
break;
case STATE_CYCLE:
hal.stream.write_all("Run");
if(sys.probing_state == Probing_Active && settings.status_report.run_substate)
probing = true;
else if (probing)
probing = probe_state.triggered;
if(sys.flags.feed_hold_pending)
hal.stream.write_all(":1");
else if(probing)
hal.stream.write_all(":2");
break;
case STATE_HOLD:
hal.stream.write_all(appendbuf(2, "Hold:", uitoa((uint32_t)(sys.holding_state - 1))));
break;
case STATE_JOG:
hal.stream.write_all("Jog");
break;
case STATE_HOMING:
hal.stream.write_all("Home");
break;
case STATE_ESTOP:
case STATE_ALARM:
if((sys.report.all || settings.status_report.alarm_substate) && sys.alarm)
hal.stream.write_all(appendbuf(2, "Alarm:", uitoa((uint32_t)sys.alarm)));
else
hal.stream.write_all("Alarm");
break;
case STATE_CHECK_MODE:
hal.stream.write_all("Check");
break;
case STATE_SAFETY_DOOR:
hal.stream.write_all(appendbuf(2, "Door:", uitoa((uint32_t)sys.parking_state)));
break;
case STATE_SLEEP:
hal.stream.write_all("Sleep");
break;
case STATE_TOOL_CHANGE:
hal.stream.write_all("Tool");
break;
}
uint_fast8_t idx;
float wco[N_AXIS];
if (!settings.status_report.machine_position || sys.report.wco) {
for (idx = 0; idx < N_AXIS; idx++) {
// Apply work coordinate offsets and tool length offset to current position.
wco[idx] = gc_get_offset(idx);
if (!settings.status_report.machine_position)
print_position[idx] -= wco[idx];
}
}
// Report position
hal.stream.write_all(settings.status_report.machine_position ? "|MPos:" : "|WPos:");
hal.stream.write_all(get_axis_values(print_position));
// Returns planner and output stream buffer states.
if (settings.status_report.buffer_state) {
hal.stream.write_all("|Bf:");
hal.stream.write_all(uitoa((uint32_t)plan_get_block_buffer_available()));
hal.stream.write_all(",");
hal.stream.write_all(uitoa(hal.stream.get_rx_buffer_free()));
}
if(settings.status_report.line_numbers) {
// Report current line number
plan_block_t *cur_block = plan_get_current_block();
if (cur_block != NULL && cur_block->line_number > 0)
hal.stream.write_all(appendbuf(2, "|Ln:", uitoa((uint32_t)cur_block->line_number)));
}
spindle_state_t sp_state = hal.spindle.get_state();
// Report realtime feed speed
if(settings.status_report.feed_speed) {
if(hal.driver_cap.variable_spindle) {
hal.stream.write_all(appendbuf(2, "|FS:", get_rate_value(st_get_realtime_rate())));
hal.stream.write_all(appendbuf(2, ",", uitoa(sp_state.on ? lroundf(sys.spindle_rpm) : 0)));
if(hal.spindle.get_data /* && sys.mpg_mode */)
hal.stream.write_all(appendbuf(2, ",", uitoa(lroundf(hal.spindle.get_data(SpindleData_RPM)->rpm))));
} else
hal.stream.write_all(appendbuf(2, "|F:", get_rate_value(st_get_realtime_rate())));
}
if(settings.status_report.pin_state) {
axes_signals_t lim_pin_state = limit_signals_merge(hal.limits.get_state());
control_signals_t ctrl_pin_state = hal.control.get_state();
if (lim_pin_state.value | ctrl_pin_state.value | probe_state.triggered | !probe_state.connected | sys.flags.block_delete_enabled) {
char *append = &buf[4];
strcpy(buf, "|Pn:");
if (probe_state.triggered)
*append++ = 'P';
if(!probe_state.connected)
*append++ = 'O';
if (lim_pin_state.value && !hal.control.get_state().limits_override)
append = axis_signals_tostring(append, lim_pin_state);
if (ctrl_pin_state.value)
append = control_signals_tostring(append, ctrl_pin_state);
*append = '\0';
hal.stream.write_all(buf);
}
}
if(settings.status_report.work_coord_offset) {
if (wco_counter > 0 && !sys.report.wco)
wco_counter--;
else
wco_counter = state_get() & (STATE_HOMING|STATE_CYCLE|STATE_HOLD|STATE_JOG|STATE_SAFETY_DOOR)
? (REPORT_WCO_REFRESH_BUSY_COUNT - 1) // Reset counter for slow refresh
: (REPORT_WCO_REFRESH_IDLE_COUNT - 1);
} else
sys.report.wco = Off;
if(settings.status_report.overrides) {
if (override_counter > 0 && !sys.report.overrides)
override_counter--;
else {
sys.report.overrides = On;
sys.report.spindle = sys.report.spindle || hal.spindle.get_state().on;
sys.report.coolant = sys.report.coolant || hal.coolant.get_state().value != 0;
override_counter = state_get() & (STATE_HOMING|STATE_CYCLE|STATE_HOLD|STATE_JOG|STATE_SAFETY_DOOR)
? (REPORT_OVERRIDE_REFRESH_BUSY_COUNT - 1) // Reset counter for slow refresh
: (REPORT_OVERRIDE_REFRESH_IDLE_COUNT - 1);
}
} else
sys.report.overrides = Off;
if(sys.report.value || gc_state.tool_change) {
if(sys.report.wco) {
hal.stream.write_all("|WCO:");
hal.stream.write_all(get_axis_values(wco));
}
if(sys.report.gwco) {
hal.stream.write_all("|WCS:G");
hal.stream.write_all(map_coord_system(gc_state.modal.coord_system.id));
}
if(sys.report.overrides) {
hal.stream.write_all(appendbuf(2, "|Ov:", uitoa((uint32_t)sys.override.feed_rate)));
hal.stream.write_all(appendbuf(2, ",", uitoa((uint32_t)sys.override.rapid_rate)));
hal.stream.write_all(appendbuf(2, ",", uitoa((uint32_t)sys.override.spindle_rpm)));
}
if(sys.report.spindle || sys.report.coolant || sys.report.tool || gc_state.tool_change) {
coolant_state_t cl_state = hal.coolant.get_state();
char *append = &buf[3];
strcpy(buf, "|A:");
if (sp_state.on)
*append++ = sp_state.ccw ? 'C' : 'S';
#if COMPATIBILITY_LEVEL == 0
if(sp_state.encoder_error && hal.driver_cap.spindle_sync)
*append++ = 'E';
#endif
if (cl_state.flood)
*append++ = 'F';
if (cl_state.mist)
*append++ = 'M';
if(gc_state.tool_change && !sys.report.tool)
*append++ = 'T';
*append = '\0';
hal.stream.write_all(buf);
}
if(sys.report.scaling) {
axis_signals_tostring(buf, gc_get_g51_state());
hal.stream.write_all("|Sc:");
hal.stream.write_all(buf);
}
if(sys.report.mpg_mode && hal.driver_cap.mpg_mode)
hal.stream.write_all(sys.mpg_mode ? "|MPG:1" : "|MPG:0");
if(sys.report.homed && (sys.homing.mask || settings.homing.flags.single_axis_commands || settings.homing.flags.manual)) {
axes_signals_t homing = {sys.homing.mask ? sys.homing.mask : AXES_BITMASK};
hal.stream.write_all(appendbuf(2, "|H:", (homing.mask & sys.homed.mask) == homing.mask ? "1" : "0"));
if(settings.homing.flags.single_axis_commands)
hal.stream.write_all(appendbuf(2, ",", uitoa(sys.homed.mask)));
}
if(sys.report.xmode && settings.mode == Mode_Lathe)
hal.stream.write_all(gc_state.modal.diameter_mode ? "|D:1" : "|D:0");
if(sys.report.tool)
hal.stream.write_all(appendbuf(2, "|T:", uitoa(gc_state.tool->tool)));
if(sys.report.tlo_reference)
hal.stream.write_all(appendbuf(2, "|TLR:", uitoa(sys.tlo_reference_set.mask != 0)));
if(sys.report.m66result && sys.var5933 > -2) { // M66 result
if(sys.var5933 >= 0)
hal.stream.write_all(appendbuf(2, "|In:", uitoa(sys.var5933)));
else
hal.stream.write_all("|In:-1");
}
}
if(grbl.on_realtime_report)
grbl.on_realtime_report(hal.stream.write_all, sys.report);
#if COMPATIBILITY_LEVEL <= 1
if(sys.report.all)
hal.stream.write_all("|FW:grblHAL");
else
#endif
if(settings.status_report.parser_state) {
static uint32_t tool;
static float feed_rate, spindle_rpm;
static gc_modal_t last_state;
static bool g92_active;
bool is_changed = feed_rate != gc_state.feed_rate || spindle_rpm != gc_state.spindle.rpm || tool != gc_state.tool->tool;
if(is_changed) {
feed_rate = gc_state.feed_rate;
tool = gc_state.tool->tool;
spindle_rpm = gc_state.spindle.rpm;
} else if ((is_changed = g92_active != is_g92_active()))
g92_active = !g92_active;
else if(memcmp(&last_state, &gc_state.modal, sizeof(gc_modal_t))) {
last_state = gc_state.modal;
is_changed = true;
}
if (is_changed)
system_set_exec_state_flag(EXEC_GCODE_REPORT);
if(sys.report.tool_offset)
system_set_exec_state_flag(EXEC_TLO_REPORT);
}
hal.stream.write_all(">" ASCII_EOL);
sys.report.value = 0;
sys.report.wco = settings.status_report.work_coord_offset && wco_counter == 0; // Set to report on next request
}
static void report_bitfield (const char *format, bool bitmap)
{
char *s;
uint_fast8_t bit = 0;
uint_fast16_t val = 1;
// Copy string from Flash to RAM, strtok cannot be used unless doing so.
if((s = (char *)malloc(strlen(format) + 1))) {
strcpy(s, format);
char *element = strtok(s, ",");
while(element) {
if(strcmp(element, "N/A")) {
hal.stream.write(ASCII_EOL);
hal.stream.write(" ");
hal.stream.write(uitoa(bit));
hal.stream.write(" - ");
if(*element)
hal.stream.write(element);
if(bitmap) {
hal.stream.write(" (");
hal.stream.write(uitoa(val));
hal.stream.write(")");
}
}
bit++;
val <<= 1;
element = strtok(NULL, ",");
}
free(s);
}
}
static void write_quoted (const char *s, const char *sep)
{
hal.stream.write("\"");
hal.stream.write(s); // TODO: escape double quoutes
hal.stream.write("\"");
if(sep)
hal.stream.write(sep);
}
static void report_settings_detail (settings_format_t format, const setting_detail_t *setting, uint_fast8_t offset)
{
switch(format)
{
case SettingsFormat_HumanReadable:
hal.stream.write("$");
hal.stream.write(uitoa(setting->id + offset));
hal.stream.write(": ");
if(setting->group == Group_Axis0)
hal.stream.write(axis_letter[offset]);
hal.stream.write(setting->name[0] == '?' ? &setting->name[1] : setting->name); // temporary hack for ? prefix...
switch(setting_datatype_to_external(setting->datatype)) {
case Format_AxisMask:
hal.stream.write(" as axismask");
break;
case Format_Bool:
hal.stream.write(" as boolean");
break;
case Format_Bitfield:
hal.stream.write(" as bitfield:");
report_bitfield(setting->format, true);
break;
case Format_XBitfield:
hal.stream.write(" as bitfield where setting bit 0 enables the rest:");
report_bitfield(setting->format, true);
break;
case Format_RadioButtons:
hal.stream.write(":");
report_bitfield(setting->format, false);
break;
case Format_IPv4:
hal.stream.write(" as IP address");
break;
default:
if(setting->unit) {
hal.stream.write(" in ");
hal.stream.write(setting->unit);
}
break;
}
if(setting->min_value && setting->max_value) {
hal.stream.write(", range: ");
hal.stream.write(setting->min_value);
hal.stream.write(" - ");
hal.stream.write(setting->max_value);
} else if(!setting_is_list(setting)) {
if(setting->min_value) {
hal.stream.write(", min: ");
hal.stream.write(setting->min_value);
}
if(setting->max_value) {
hal.stream.write(", max: ");
hal.stream.write(setting->max_value);
}
}
break;
case SettingsFormat_MachineReadable:
hal.stream.write("[SETTING:");
hal.stream.write(uitoa(setting->id + offset));
hal.stream.write(vbar);
hal.stream.write(uitoa(setting->group + (setting->group == Group_Axis0 ? offset : 0)));
hal.stream.write(vbar);
if(setting->group == Group_Axis0)
hal.stream.write(axis_letter[offset]);
hal.stream.write(setting->name[0] == '?' ? &setting->name[1] : setting->name); // temporary hack for ? prefix...
hal.stream.write(vbar);
if(setting->unit)
hal.stream.write(setting->unit);
hal.stream.write(vbar);
hal.stream.write(uitoa(setting_datatype_to_external(setting->datatype)));
hal.stream.write(vbar);
if(setting->format)
hal.stream.write(setting->format);
hal.stream.write(vbar);
if(setting->min_value && !setting_is_list(setting))
hal.stream.write(setting->min_value);
hal.stream.write(vbar);
if(setting->max_value)
hal.stream.write(setting->max_value);
hal.stream.write("]");
break;
case SettingsFormat_Grbl:
{
write_quoted(uitoa(setting->id + offset), ",");
hal.stream.write("\"");
if(setting->group == Group_Axis0)
hal.stream.write(axis_letter[offset]);
hal.stream.write(setting->name[0] == '?' ? &setting->name[1] : setting->name); // temporary hack for ? prefix...
hal.stream.write("\",");
if(setting->unit) {
write_quoted(setting->unit, ",");
} else // TODO: output sensible unit from datatype
write_quoted("", ",");
#ifndef NO_SETTINGS_DESCRIPTIONS
const char *description = setting_get_description((setting_id_t)(setting->id + offset));
write_quoted(description ? description : "", ",");
#else
write_quoted("", NULL);
#endif
}
break;
case SettingsFormat_grblHAL:
{
hal.stream.write(uitoa(setting->id + offset));
hal.stream.write("\t");
if(setting->group == Group_Axis0)
hal.stream.write(axis_letter[offset]);
hal.stream.write(setting->name[0] == '?' ? &setting->name[1] : setting->name); // temporary hack for ? prefix...
hal.stream.write("\t");
if(setting->unit)
hal.stream.write(setting->unit);
else if(setting->datatype == Format_AxisMask || setting->datatype == Format_Bitfield || setting->datatype == Format_XBitfield)
hal.stream.write("mask");
else if(setting->datatype == Format_Bool)
hal.stream.write("boolean");
else if(setting->datatype == Format_RadioButtons)
hal.stream.write("integer");
hal.stream.write("\t");
/*
Format_Bool = 0,
Format_Bitfield,
Format_XBitfield,
Format_RadioButtons,
Format_AxisMask,
Format_Integer, // 32 bit
,
Format_String,
Format_Password,
Format_IPv4,
// For internal use only
Format_Int8,
Format_Int16,
*/
switch(setting_datatype_to_external(setting->datatype)) {
case Format_Integer:
hal.stream.write("integer");
break;
case Format_Decimal:
hal.stream.write("float");
break;
case Format_Bool:
hal.stream.write("bool");
break;
case Format_AxisMask:
case Format_Bitfield:
hal.stream.write("bitfield");
break;
case Format_XBitfield:
hal.stream.write("xbitfield");
break;
case Format_RadioButtons:
hal.stream.write("radiobuttons");
break;
case Format_IPv4:
hal.stream.write("ipv4");
break;
case Format_String:
hal.stream.write("string");
break;
case Format_Password:
hal.stream.write("password");
break;
default:
break;
}
hal.stream.write("\t");
if(setting->format)
hal.stream.write(setting->format);
else if (setting->datatype == Format_AxisMask)
hal.stream.write("axes");
hal.stream.write("\t");
#ifndef NO_SETTINGS_DESCRIPTIONS
const char *description = setting_get_description((setting_id_t)(setting->id + offset));
hal.stream.write(description ? description : "");
#endif
hal.stream.write("\t");
if(setting->min_value)
hal.stream.write(setting->min_value);
hal.stream.write("\t");
if(setting->max_value)
hal.stream.write(setting->max_value);
}
break;
}
hal.stream.write(ASCII_EOL);
}
typedef struct {
settings_format_t format;
setting_group_t group;
uint_fast16_t offset;
} report_args_t;
static bool print_sorted (const setting_detail_t *setting, uint_fast16_t offset, void *args)
{
if(!(((report_args_t *)args)->group == setting->group && ((report_args_t *)args)->offset != offset))
report_settings_detail (((report_args_t *)args)->format, setting, offset);
return true;
}
static status_code_t sort_settings_details (settings_format_t format, setting_group_t group)
{
bool reported = group == Group_All;
setting_details_t *details = settings_get_details();
uint16_t n_settings = details->n_settings;
const setting_detail_t *setting;
report_args_t args;
args.group = settings_normalize_group(group);
args.offset = group - args.group;
args.format = format;
while(details->on_get_settings) {
details = details->on_get_settings();
n_settings += details->n_settings;
}
setting_detail_t **all_settings, **psetting;
if((all_settings = calloc(n_settings, sizeof(setting_detail_t *)))) {
uint_fast16_t idx;
details = settings_get_details();
psetting = all_settings;
n_settings = 0;
for(idx = 0; idx < details->n_settings; idx++) {
setting = &details->settings[idx];
if((group == Group_All || setting->group == args.group) && (setting->is_available == NULL ||setting->is_available(setting))) {
*psetting++ = (setting_detail_t *)setting;
n_settings++;
}
}
while(details->on_get_settings) {
details = details->on_get_settings();
for(idx = 0; idx < details->n_settings; idx++) {
setting = &details->settings[idx];
if((group == Group_All || setting->group == args.group) && (setting->is_available == NULL ||setting->is_available(setting))) {
*psetting++ = (setting_detail_t *)setting;
n_settings++;
}
}
}
qsort(all_settings, n_settings, sizeof(setting_detail_t *), cmp_settings);
if(format == SettingsFormat_Grbl)
hal.stream.write("\"$-Code\",\" Setting\",\" Units\",\" Setting Description\"" ASCII_EOL);
else if(format == SettingsFormat_grblHAL)
hal.stream.write("$-Code\tSetting\tUnits\tDatatype\tData format\tSetting Description\tMin\tMax" ASCII_EOL);
for(idx = 0; idx < n_settings; idx++) {
if(settings_iterator(all_settings[idx], print_sorted, &args))
reported = true;
}
free(all_settings);
}
return all_settings == NULL ? Status_Unhandled : (reported ? Status_OK : Status_SettingDisabled);
}
static bool print_unsorted (const setting_detail_t *setting, uint_fast16_t offset, void *args)
{
if(!(((report_args_t *)args)->group == setting->group && ((report_args_t *)args)->offset != offset) &&
(setting->is_available == NULL ||setting->is_available(setting)))
report_settings_detail(((report_args_t *)args)->format, setting, offset);
return true;
}
static status_code_t print_settings_details (settings_format_t format, setting_group_t group, uint_fast16_t axis_rpt)
{
status_code_t status;
if((status = sort_settings_details(format, group)) != Status_Unhandled)
return status;
bool reported = group == Group_All;
uint_fast16_t idx;
const setting_detail_t *setting;
setting_details_t *settings = settings_get_details();
report_args_t args;
args.group = settings_normalize_group(group);
args.offset = group - args.group;
args.format = format;
do {
for(idx = 0; idx < settings->n_settings; idx++) {
setting = &settings->settings[idx];
if(group == Group_All || setting->group == args.group) {
if(settings_iterator(setting, print_unsorted, &args))
reported = true;
}
}
settings = settings->on_get_settings ? settings->on_get_settings() : NULL;
} while(settings);
return reported ? Status_OK : Status_SettingDisabled;
}
status_code_t report_settings_details (settings_format_t format, setting_id_t id, setting_group_t group)
{
uint_fast16_t axis_rpt = 0;
if(id != Setting_SettingsAll) {
status_code_t status = Status_OK;
const setting_detail_t *setting = setting_get_details(id, NULL);
if(setting)
report_settings_detail(format, setting, id - setting->id);
else
status = Status_SettingDisabled;
return status;
}
return print_settings_details(format, group, axis_rpt);
}
#ifndef NO_SETTINGS_DESCRIPTIONS
status_code_t report_setting_description (settings_format_t format, setting_id_t id)
{
const char *description = setting_get_description(id);
if(format == SettingsFormat_MachineReadable) {
hal.stream.write("[SETTINGDESCR:");
hal.stream.write(uitoa(id));
hal.stream.write(vbar);
}
// hal.stream.write(description == NULL ? (is_setting_available(setting_get_details(id, NULL)) ? "" : "N/A") : description); // TODO?
hal.stream.write(description == NULL ? (setting_get_details(id, NULL) ? "" : "N/A") : description);
if(format == SettingsFormat_MachineReadable)
hal.stream.write("]" ASCII_EOL);
return Status_OK;
}
#endif
status_code_t report_alarm_details (bool grbl_format)
{
uint_fast16_t idx;
alarm_details_t *list = alarms_get_details();
if(grbl_format)
hal.stream.write("\"Alarm Code in v1.1+\",\" Alarm Message in v1.0-\",\" Alarm Description\"" ASCII_EOL);
do { // TODO: add sorting?
for(idx = 0; idx < list->n_alarms; idx++) {
if(grbl_format) {
write_quoted(uitoa(list->alarms[idx].id), ",");
write_quoted(list->alarms[idx].name, ",");
write_quoted(list->alarms[idx].description ? list->alarms[idx].description : "", NULL);
hal.stream.write(ASCII_EOL);
} else {
hal.stream.write("[ALARMCODE:");
hal.stream.write(uitoa(list->alarms[idx].id));
hal.stream.write(vbar);
hal.stream.write(list->alarms[idx].name);
hal.stream.write(vbar);
if(list->alarms[idx].description)
hal.stream.write(list->alarms[idx].description);
hal.stream.write("]" ASCII_EOL);
}
}
list = list->on_get_alarms ? list->on_get_alarms() : NULL;
} while(list);
return Status_OK;
}
status_code_t report_error_details (bool grbl_format)
{
uint_fast16_t idx;
error_details_t *list = errors_get_details();
if(grbl_format)
hal.stream.write("\"Error Code in v1.1+\",\"Error Message in v1.0-\",\"Error Description\"" ASCII_EOL);
do { // TODO: add sorting?
for(idx = 0; idx < list->n_errors; idx++) {
if(grbl_format) {
write_quoted(uitoa(list->errors[idx].id), ",");
write_quoted(list->errors[idx].name, ",");
write_quoted(list->errors[idx].description ? list->errors[idx].description : "", NULL);
hal.stream.write(ASCII_EOL);
} else {
hal.stream.write("[ERRORCODE:");
hal.stream.write(uitoa(list->errors[idx].id));
hal.stream.write(vbar);
hal.stream.write(list->errors[idx].name);
hal.stream.write(vbar);
if(list->errors[idx].description)
hal.stream.write(list->errors[idx].description);
hal.stream.write("]" ASCII_EOL);
}
}
list = list->on_get_errors ? list->on_get_errors() : NULL;
} while(list);
return Status_OK;
}
static void print_setting_group (const setting_group_detail_t *group, char *prefix)
{
if(settings_is_group_available(group->id)) {
if(!prefix) {
hal.stream.write("[SETTINGGROUP:");
hal.stream.write(uitoa(group->id));
hal.stream.write(vbar);
hal.stream.write(uitoa(group->parent));
hal.stream.write(vbar);
hal.stream.write(group->name);
hal.stream.write("]" ASCII_EOL);
} else if(group->id != Group_Root && settings_is_group_available(group->id)) {
hal.stream.write(prefix);
hal.stream.write(group->name);
hal.stream.write(ASCII_EOL);
}
}
}
static int cmp_setting_group_id (const void *a, const void *b)
{
return (*(setting_detail_t **)(a))->id - (*(setting_detail_t **)(b))->id;
}
static int cmp_setting_group_name (const void *a, const void *b)
{
return strcmp((*(setting_detail_t **)(a))->name, (*(setting_detail_t **)(b))->name);
}
static status_code_t sort_setting_group_details (bool by_id, char *prefix)
{
setting_details_t *details = settings_get_details();
uint8_t n_groups = details->n_groups;
while(details->on_get_settings) {
details = details->on_get_settings();
n_groups += details->n_groups;
}
setting_group_detail_t **all_groups, **group;
if((all_groups = calloc(n_groups, sizeof(setting_group_detail_t *)))) {
uint_fast16_t idx;
group = all_groups;
details = settings_get_details();
do {
for(idx = 0; idx < details->n_groups; idx++)
*group++ = (setting_group_detail_t *)&details->groups[idx];
details = details->on_get_settings ? details->on_get_settings() : NULL;
} while(details);
qsort(all_groups, n_groups, sizeof(setting_group_detail_t *), by_id ? cmp_setting_group_id : cmp_setting_group_name);
for(idx = 0; idx < n_groups; idx++)
print_setting_group(all_groups[idx], prefix);
free(all_groups);
}
return all_groups == NULL ? Status_Unhandled : Status_OK;
}
status_code_t report_setting_group_details (bool by_id, char *prefix)
{
if(sort_setting_group_details(by_id, prefix) != Status_Unhandled)
return Status_OK;
uint_fast16_t idx;
setting_details_t *details = settings_get_details();
do {
for(idx = 0; idx < details->n_groups; idx++)
print_setting_group(&details->groups[idx], prefix);
details = details->on_get_settings ? details->on_get_settings() : NULL;
} while(details);
return Status_OK;
}
static char *add_limits (char *buf, limit_signals_t limits)
{
buf = axis_signals_tostring(buf, limits.min);
*buf++ = ',';
buf = axis_signals_tostring(buf, limits.max);
*buf++ = ',';
buf = axis_signals_tostring(buf, limits.min2);
*buf++ = ',';
buf = axis_signals_tostring(buf, limits.max2);
return buf;
}
status_code_t report_last_signals_event (sys_state_t state, char *args)
{
char *append = &buf[12];
strcpy(buf, "[LASTEVENTS:");
append = control_signals_tostring(append, sys.last_event.control);
*append++ = ',';
append = add_limits(append, sys.last_event.limits);
hal.stream.write(buf);
hal.stream.write("]" ASCII_EOL);
return Status_OK;
}
status_code_t report_current_limit_state (sys_state_t state, char *args)
{
char *append = &buf[8];
strcpy(buf, "[LIMITS:");
append = add_limits(append, hal.limits.get_state());
hal.stream.write(buf);
hal.stream.write("]" ASCII_EOL);
return Status_OK;
}
// Prints spindle data (encoder pulse and index count, angular position).
status_code_t report_spindle_data (sys_state_t state, char *args)
{
if(hal.spindle.get_data) {
float apos = hal.spindle.get_data(SpindleData_AngularPosition)->angular_position;
spindle_data_t *spindle = hal.spindle.get_data(SpindleData_Counters);
hal.stream.write("[SPINDLE:");
hal.stream.write(uitoa(spindle->index_count));
hal.stream.write(",");
hal.stream.write(uitoa(spindle->pulse_count));
hal.stream.write(",");
hal.stream.write(uitoa(spindle->error_count));
hal.stream.write(",");
hal.stream.write(ftoa(apos, 3));
hal.stream.write("]" ASCII_EOL);
}
return hal.spindle.get_data ? Status_OK : Status_InvalidStatement;
}
static const char *get_pinname (pin_function_t function)
{
const char *name = NULL;
uint_fast8_t idx = sizeof(pin_names) / sizeof(pin_name_t);
do {
if(pin_names[--idx].function == function)
name = pin_names[idx].name;
} while(idx && !name);
return name ? name : "N/A";
}
static void report_pin (xbar_t *pin)
{
hal.stream.write("[PIN:");
if(pin->port)
hal.stream.write((char *)pin->port);
hal.stream.write(uitoa(pin->pin));
hal.stream.write(",");
hal.stream.write(get_pinname(pin->function));
if(pin->description) {
hal.stream.write(",");
hal.stream.write(pin->description);
}
hal.stream.write("]" ASCII_EOL);
}
status_code_t report_pins (sys_state_t state, char *args)
{
if(hal.enumerate_pins)
hal.enumerate_pins(false, report_pin);
return Status_OK;
}
void report_pid_log (void)
{
#ifdef PID_LOG
uint_fast16_t idx = 0;
hal.stream.write("[PID:");
hal.stream.write(ftoa(sys.pid_log.setpoint, N_DECIMAL_PIDVALUE));
hal.stream.write(",");
hal.stream.write(ftoa(sys.pid_log.t_sample, N_DECIMAL_PIDVALUE));
hal.stream.write(",2|"); // 2 is number of values per sample!
if(sys.pid_log.idx) do {
hal.stream.write(ftoa(sys.pid_log.target[idx], N_DECIMAL_PIDVALUE));
hal.stream.write(",");
hal.stream.write(ftoa(sys.pid_log.actual[idx], N_DECIMAL_PIDVALUE));
idx++;
if(idx != sys.pid_log.idx)
hal.stream.write(",");
} while(idx != sys.pid_log.idx);
hal.stream.write("]" ASCII_EOL);
grbl.report.status_message(Status_OK);
#else
grbl.report.status_message(Status_GcodeUnsupportedCommand);
#endif
}