1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-09-02 19:02:35 +02:00

added settings class

This commit is contained in:
bdring
2020-04-17 18:11:49 -05:00
parent a6309198fa
commit 1a8ff1f3a4

715
Grbl_Esp32/wmb_settings.cpp Normal file
View File

@@ -0,0 +1,715 @@
#include <iostream>
#include <map>
#include <string>
#include <string.h>
#include <typeinfo>
#include <iterator>
#include "./defaults.h"
using namespace std;
// These status values are just assigned at random, for testing
// they need to be synced with the rest of Grbl
typedef enum {
STATUS_OK = 0,
STATUS_INVALID_STATEMENT,
STATUS_NUMBER_RANGE,
STATUS_INVALID_VALUE,
STATUS_IDLE_ERROR,
} err_t;
enum {
BITFLAG_INVERT_ST_ENABLE = 1,
BITFLAG_INVERT_LIMIT_PINS = 2,
BITFLAG_INVERT_PROBE_PIN = 4,
BITFLAG_REPORT_INCHES = 8,
BITFLAG_SOFT_LIMIT_ENABLE = 16,
BITFLAG_HARD_LIMIT_ENABLE = 32,
BITFLAG_HOMING_ENABLE = 64,
BITFLAG_LASER_MODE = 128,
};
#define DEFAULT_SPINDLE_TYPE 0
#define DEFAULT_SPINDLE_BIT_PRECISION 10
enum {
STATE_IDLE = 0,
STATE_JOG,
};
#define MAXLINE 128
int client;
struct {
int flags;
} settings;
struct {
int state;
} sys;
#define MAX_N_AXIS 6
#define MESSAGE_RESTORE_DEFAULTS "restoring defaults"
enum {
SETTINGS_RESTORE_DEFAULTS,
SETTINGS_RESTORE_PARAMETERS,
SETTINGS_RESTORE_ALL,
SETTINGS_RESTORE_WIFI_SETTINGS,
SETTINGS_WIPE,
};
err_t my_spindle_init();
err_t limits_init();
err_t also_soft_limit();
void settings_restore(int);
void report_feedback_message(const char *);
err_t report_normal_settings(uint8_t);
err_t report_extended_settings(uint8_t);
err_t report_gcode_modes(uint8_t);
err_t report_build_info(uint8_t);
err_t report_startup_lines(uint8_t);
err_t toggle_check_mode(uint8_t);
err_t disable_alarm_lock(uint8_t);
err_t report_ngc(uint8_t);
err_t home_all(uint8_t);
err_t home_x(uint8_t);
err_t home_y(uint8_t);
err_t home_z(uint8_t);
err_t home_a(uint8_t);
err_t home_b(uint8_t);
err_t home_c(uint8_t);
err_t sleep(uint8_t);
err_t gc_execute_line(char *line, uint8_t client);
void mc_reset();
err_t check_motor_settings() { return STATUS_OK; }
err_t settings_spi_driver_init() { return STATUS_OK; }
// SettingsList is a linked list of all settings,
// so common code can enumerate them.
class Setting;
Setting *SettingsList = NULL;
// This abstract class defines the generic interface that
// is used to set and get values for all settings independent
// of their underlying data type. The values are always
// represented as human-readable strings. This generic
// interface is used for managing settings via the user interface.
class Setting {
public:
// Add each constructed setting to the linked list
Setting *link;
Setting(const char* name) {
displayName = name;
link = SettingsList;
SettingsList = this;
}
const char* getName() { return displayName; };
// load() reads the backing store to get the current
// value of the setting. This could be slow so it
// should be done infrequently, typically once at startup.
virtual void load() =0;
// commit() puts the current value of the setting into
// the backing store.
// virtual void commit() =0;
virtual err_t setStringValue(string value) =0;
virtual string getStringValue() =0;
private:
const char* displayName;
};
// This template class, derived from the generic abstract
// class, lets you store an individual setting as typed data.
template<typename T>
class TypedSetting : public Setting {
public:
TypedSetting(const char* name, T defVal, T minVal, T maxVal, err_t (*f)() = NULL) :
Setting(name),
defaultValue(defVal),
currentValue(defVal),
minValue(minVal),
maxValue(maxVal),
checker(f)
{ }
void load() {
if (isfloat) {
//p storedValue = preferences.getFloat(displayName, defaultValue);
} else {
//p storedValue = preferences.getInt(displayName, defaultValue);
} // Read value from backing store
currentValue = storedValue;
}
void commit() {
if (storedValue != currentValue) {
if (storedValue == defaultValue) {
//p preferences.remove(displayName);
} else {
if (isfloat) {
//p preferences.putFloat(displayName, currentValue);
} else {
//p preferences.putInt(displayName, currentValue);
}
}
}
}
T get() { return currentValue; }
err_t set(T value) { currentValue = value; return STATUS_OK; }
err_t string_to_value(string s, T &value) {
T convertedValue;
try {
convertedValue = stof(s);
}
catch (int e) {
return STATUS_INVALID_VALUE;
}
if (convertedValue < minValue || convertedValue > maxValue) {
return STATUS_NUMBER_RANGE;
}
value = convertedValue;
return STATUS_OK;
}
string value_to_string (T value) {
return to_string(value);
}
// Standard methods of the abstract class "Setting"
err_t setStringValue(string s) {
T newValue;
err_t ret = string_to_value(s, newValue);
if (ret) return ret;
if (checker && (ret = checker())) return ret;
currentValue = newValue;
return ret;
}
string getStringValue () {
return to_string(get());
}
private:
T defaultValue;
T currentValue;
T storedValue;
T minValue;
T maxValue;
const bool isfloat = typeid(T) == typeid(float);
err_t (*checker)();
};
typedef TypedSetting<float> FloatSetting;
typedef TypedSetting<int> IntSetting;
class AxisSettings {
public:
string name;
FloatSetting steps_per_mm;
FloatSetting max_rate;
FloatSetting acceleration;
FloatSetting max_travel;
FloatSetting run_current;
FloatSetting hold_current;
IntSetting microsteps;
IntSetting stallguard;
AxisSettings(string axisName, float steps, float rate, float accel, float travel,
float run, float hold, int usteps, int stall) :
name(axisName),
steps_per_mm((axisName+"_STEPS_PER_MM").c_str(), steps, 1.0, 50000.0, check_motor_settings),
max_rate((axisName+"_MAX_RATE").c_str(), rate, 1.0, 1000000.0, check_motor_settings),
acceleration((axisName+"_ACCELERATION").c_str(), accel, 1.0, 100000.0),
max_travel((axisName+"_MAX_TRAVEL").c_str(), travel, 1.0, 100000.0), // Note! this values is entered as scaler but store negative
run_current((axisName+"_RUN_CURRENT").c_str(), run, 0.05, 20.0, settings_spi_driver_init),
hold_current((axisName+"_HOLD_CURRENT").c_str(), hold, 0.0, 100.0, settings_spi_driver_init),
microsteps((axisName+"_MICROSTEPS").c_str(), usteps, 1, 256, settings_spi_driver_init),
stallguard((axisName+"_STALLGUARD").c_str(), stall, 0, 100, settings_spi_driver_init)
{}
};
AxisSettings x_axis_settings = {
"X",
DEFAULT_X_STEPS_PER_MM,
DEFAULT_X_MAX_RATE,
DEFAULT_X_ACCELERATION,
DEFAULT_X_MAX_TRAVEL,
DEFAULT_X_CURRENT,
DEFAULT_X_HOLD_CURRENT,
DEFAULT_X_MICROSTEPS,
DEFAULT_X_STALLGUARD
};
AxisSettings y_axis_settings = {
"Y",
DEFAULT_Y_STEPS_PER_MM,
DEFAULT_Y_MAX_RATE,
DEFAULT_Y_ACCELERATION,
DEFAULT_Y_MAX_TRAVEL,
DEFAULT_Y_CURRENT,
DEFAULT_Y_HOLD_CURRENT,
DEFAULT_Y_MICROSTEPS,
DEFAULT_Y_STALLGUARD
};
AxisSettings z_axis_settings = {
"Z",
DEFAULT_Z_STEPS_PER_MM,
DEFAULT_Z_MAX_RATE,
DEFAULT_Z_ACCELERATION,
DEFAULT_Z_MAX_TRAVEL,
DEFAULT_Z_CURRENT,
DEFAULT_Z_HOLD_CURRENT,
DEFAULT_Z_MICROSTEPS,
DEFAULT_Z_STALLGUARD
};
AxisSettings a_axis_settings = {
"A",
DEFAULT_A_STEPS_PER_MM,
DEFAULT_A_MAX_RATE,
DEFAULT_A_ACCELERATION,
DEFAULT_A_MAX_TRAVEL,
DEFAULT_A_CURRENT,
DEFAULT_A_HOLD_CURRENT,
DEFAULT_A_MICROSTEPS,
DEFAULT_A_STALLGUARD
};
AxisSettings b_axis_settings = {
"B",
DEFAULT_B_STEPS_PER_MM,
DEFAULT_B_MAX_RATE,
DEFAULT_B_ACCELERATION,
DEFAULT_B_MAX_TRAVEL,
DEFAULT_B_CURRENT,
DEFAULT_B_HOLD_CURRENT,
DEFAULT_B_MICROSTEPS,
DEFAULT_B_STALLGUARD
};
AxisSettings c_axis_settings = {
"C",
DEFAULT_C_STEPS_PER_MM,
DEFAULT_C_MAX_RATE,
DEFAULT_C_ACCELERATION,
DEFAULT_C_MAX_TRAVEL,
DEFAULT_C_CURRENT,
DEFAULT_C_HOLD_CURRENT,
DEFAULT_C_MICROSTEPS,
DEFAULT_C_STALLGUARD
};
AxisSettings axis_settings[] = {
x_axis_settings,
y_axis_settings,
z_axis_settings,
a_axis_settings,
b_axis_settings,
c_axis_settings,
};
class StringSetting : public Setting {
private:
string currentValue;
string storedValue;
string defaultValue;
public:
StringSetting(const char* name, string defVal) :
Setting(name),
defaultValue(defVal),
currentValue(defVal)
{ };
void load() {
//p storedValue = preferences.getString(displayName, defaultValue);
currentValue = storedValue;
}
void commit() {
if (storedValue != currentValue) {
if (storedValue == defaultValue) {
//p preferences.remove(displayName);
} else {
//p preferences.putString(displayName, currentValue);
}
}
}
string get() { return currentValue; }
err_t set(string value) { currentValue = value; return STATUS_OK; }
err_t string_to_value(string s, string &value) {
value = s;
return STATUS_OK;
}
string value_to_string (string value) { return value; }
err_t setStringValue(string s) {
currentValue = s;
return STATUS_OK;
}
string getStringValue() { return currentValue; }
};
StringSetting startup_line_0("N0", "");
StringSetting startup_line_1("N1", "");
StringSetting build_info("I", "");
// Single-use class for the $RST= command
// There is no underlying stored value
class SettingsReset : public Setting {
public:
SettingsReset(const char* name) :
Setting(name)
{ }
void load() {
}
void commit() {
}
string get() { return 0; }
err_t set(int value) {
settings_restore(value);
report_feedback_message(MESSAGE_RESTORE_DEFAULTS);
mc_reset(); // Ensure settings are initialized correctly
return STATUS_OK;
}
err_t string_to_value(string s, int &value) {
switch (s[0]) {
case '$': value = SETTINGS_RESTORE_DEFAULTS; break;
case '#': value = SETTINGS_RESTORE_PARAMETERS; break;
case '*': value = SETTINGS_RESTORE_ALL; break;
case '@': value = SETTINGS_RESTORE_WIFI_SETTINGS; break;
case '!': value = SETTINGS_WIPE; break;
default: return STATUS_INVALID_VALUE;
}
return STATUS_OK;
}
err_t setStringValue(string s) {
int value;
err_t ret = string_to_value(s, value);
return ret ? ret : set(value);
}
string value_to_string (int value) {
switch(value) {
case SETTINGS_RESTORE_DEFAULTS: return "$"; break;
case SETTINGS_RESTORE_PARAMETERS: return "#"; break;
case SETTINGS_RESTORE_ALL: return "*"; break;
case SETTINGS_RESTORE_WIFI_SETTINGS: return "@"; break;
case SETTINGS_WIPE: return "!"; break;
default: return "";
}
}
string getStringValue() { return ""; }
};
IntSetting pulse_microseconds("STEP_PULSE", DEFAULT_STEP_PULSE_MICROSECONDS, 3, 1000);
IntSetting stepper_idle_lock_time("STEPPER_IDLE_TIME", DEFAULT_STEPPER_IDLE_LOCK_TIME, 0, 255);
IntSetting step_invert_mask("STEP_INVERT_MASK", DEFAULT_STEPPING_INVERT_MASK, 0, (1<<MAX_N_AXIS)-1);
IntSetting dir_invert_mask("DIR_INVERT_MASK", DEFAULT_DIRECTION_INVERT_MASK, 0, (1<<MAX_N_AXIS)-1);
// XXX need to call st_generate_step_invert_masks()
IntSetting homing_dir_mask("HOMING_DIR_INVERT_MASK", DEFAULT_HOMING_DIR_MASK, 0, (1<<MAX_N_AXIS)-1);
class FlagSetting : public Setting {
private:
bool defaultValue;
bool storedValue;
bool currentValue;
uint32_t bitMask;
err_t (*checker)();
public:
FlagSetting(const char* name, bool defVal, uint32_t mask, err_t (*f)() = NULL) :
Setting(name),
defaultValue(defVal),
bitMask(mask),
checker(f)
{ }
void load() {
//p storedValue = preferences.getBool(displayName, defaultValue);
currentValue = storedValue;
}
void commit() {
if (storedValue != currentValue) {
if (storedValue == defaultValue) {
//p preferences.remove(displayName);
} else {
//p preferences.putBool(displayName, currentValue);
}
}
}
bool get() { return currentValue; }
err_t set(bool value) {
currentValue = value;
if (value) {
settings.flags |= bitMask;
} else {
settings.flags |= ~bitMask;
}
return STATUS_OK;
}
err_t string_to_value(string s, bool &value) {
int n;
try {
n = stoi(s);
}
catch (int e) {
return STATUS_INVALID_VALUE;
}
value = !!n;
return STATUS_OK;
}
err_t setStringValue(string s) {
bool value;
err_t ret = string_to_value(s, value);
if (checker && (ret = checker())) return ret;
return ret ? ret : set(value);
}
string value_to_string (bool value) {
return to_string(value);
}
string getStringValue() { return value_to_string(get()); }
};
FlagSetting step_enable_invert("STEP_ENABLE_INVERT", DEFAULT_INVERT_ST_ENABLE, BITFLAG_INVERT_ST_ENABLE);
FlagSetting limit_invert("LIMIT_INVERT", DEFAULT_INVERT_LIMIT_PINS, BITFLAG_INVERT_LIMIT_PINS);
FlagSetting probe_invert("PROBE_INVERT", DEFAULT_INVERT_PROBE_PIN, BITFLAG_INVERT_PROBE_PIN);
FlagSetting report_inches("REPORT_INCHES", DEFAULT_REPORT_INCHES, BITFLAG_REPORT_INCHES);
err_t check_homing_enable() {
return STATUS_OK;
}
FlagSetting soft_limits("SOFT_LIMITS", DEFAULT_SOFT_LIMIT_ENABLE, BITFLAG_SOFT_LIMIT_ENABLE, check_homing_enable);
// XXX need to check for HOMING_ENABLE
FlagSetting hard_limits("HARD_LIMITS", DEFAULT_HARD_LIMIT_ENABLE, BITFLAG_HARD_LIMIT_ENABLE, limits_init);
// XXX need to call limits_init();
FlagSetting homing_enable("HOMING_ENABLE", DEFAULT_HOMING_ENABLE, BITFLAG_HOMING_ENABLE, also_soft_limit);
// XXX also need to clear, but not set, BITFLAG_SOFT_LIMIT_ENABLE
FlagSetting laser_mode("LASER_MODE", DEFAULT_LASER_MODE, BITFLAG_LASER_MODE, my_spindle_init);
// XXX also need to call my_spindle->init();
IntSetting status_mask("STATUS_MASK", DEFAULT_STATUS_REPORT_MASK, 0, 2);
FloatSetting junction_deviation("JUNCTION_DEVIATION", DEFAULT_JUNCTION_DEVIATION, 0, 10);
FloatSetting arc_tolerance("ARC_TOLERANCE", DEFAULT_ARC_TOLERANCE, 0, 1);
FloatSetting homing_feed_rate("HOMING_FEED", DEFAULT_HOMING_FEED_RATE, 0, 10000);
FloatSetting homing_seek_rate("HOMING_SEEK", DEFAULT_HOMING_SEEK_RATE, 0, 10000);
FloatSetting homing_debounce("HOMING_DEBOUNCE", DEFAULT_HOMING_DEBOUNCE_DELAY, 0, 10000);
FloatSetting homing_pulloff("HOMING_PULLOFF", DEFAULT_HOMING_PULLOFF, 0, 1000);
FloatSetting spindle_pwm_freq("SPINDLE_PWM_FREQ", DEFAULT_SPINDLE_FREQ, 0, 100000);
FloatSetting rpm_max("RPM_MAX", DEFAULT_SPINDLE_RPM_MAX, 0, 100000);
FloatSetting rpm_min("RPM_MIN", DEFAULT_SPINDLE_RPM_MIN, 0, 100000);
FloatSetting spindle_pwm_off_value("SPINDLE_PWM_OFF_VALUE", DEFAULT_SPINDLE_OFF_VALUE, 0.0, 100.0); // these are percentages
FloatSetting spindle_pwm_min_value("SPINDLE_PWM_MIN_VALUE", DEFAULT_SPINDLE_MIN_VALUE, 0.0, 100.0);
FloatSetting spindle_pwm_max_value("SPINDLE_PWM_MAX_VALUE", DEFAULT_SPINDLE_MAX_VALUE, 0.0, 100.0);
IntSetting spindle_pwm_bit_precision("SPINDLE_PWM_BIT_PRECISION", DEFAULT_SPINDLE_BIT_PRECISION, 1, 16);
StringSetting spindle_type("SPINDLE_TYPE", DEFAULT_SPINDLE_TYPE);
// The following table maps numbered settings to their settings
// objects, for backwards compatibility. It is used if the
// line is of the form "$key=value". If a key match is found,
// the object's setValueString method is invoked with the value
// as argument. If no match is found, the list of named settings
// is searched, with the same behavior on a match.
// These are compatibily aliases for Classic GRBL
std::map<const char*, Setting*> numberedSettings = {
{ "0", &pulse_microseconds },
{ "1", &stepper_idle_lock_time },
{ "2", &step_invert_mask },
{ "3", &dir_invert_mask },
{ "4", &step_enable_invert },
{ "5", &limit_invert },
{ "6", &probe_invert },
{ "10", &status_mask },
{ "11", &junction_deviation },
{ "12", &arc_tolerance },
{ "13", &report_inches },
{ "20", &soft_limits },
{ "21", &hard_limits },
{ "22", &homing_enable },
{ "23", &homing_dir_mask },
{ "24", &homing_feed_rate },
{ "25", &homing_seek_rate },
{ "26", &homing_debounce },
{ "27", &homing_pulloff },
{ "30", &rpm_max },
{ "31", &rpm_min },
{ "32", &laser_mode },
{ "100", &x_axis_settings.steps_per_mm },
{ "101", &y_axis_settings.steps_per_mm },
{ "102", &z_axis_settings.steps_per_mm },
{ "110", &x_axis_settings.max_rate },
{ "111", &y_axis_settings.max_rate },
{ "112", &z_axis_settings.max_rate },
{ "120", &x_axis_settings.acceleration },
{ "121", &y_axis_settings.acceleration },
{ "122", &z_axis_settings.acceleration },
{ "130", &x_axis_settings.max_travel },
{ "131", &y_axis_settings.max_travel },
{ "132", &z_axis_settings.max_travel },
};
// FIXME - jog may need to be special-cased in the parser, since
// it is not really a setting and the entire line needs to be
// sent to gc_execute_line. It is probably also more time-critical
// than actual settings, which change infrequently, so handling
// it early is probably prudent.
uint8_t jog_set(uint8_t *value, uint8_t client) {
// Execute only if in IDLE or JOG states.
if (sys.state != STATE_IDLE && sys.state != STATE_JOG) return STATUS_IDLE_ERROR;
// restore the $J= prefix because gc_execute_line() expects it
char line[MAXLINE];
strcpy(line, "$J=");
strncat(line, (char *)value, MAXLINE-strlen("$J=")-1);
return gc_execute_line(line, client); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions.
}
// The following table is used if the line is of the form "$key\n"
// i.e. dollar commands without "="
// The key value is matched against the string and the corresponding
// function is called with no arguments.
// If there is no key match an error is reported
typedef err_t (*Command_t)(uint8_t);
std::map<const char*, Command_t> dollarCommands = {
{ "$", report_normal_settings },
{ "+", report_extended_settings },
{ "G", report_gcode_modes },
{ "C", toggle_check_mode },
{ "X", disable_alarm_lock },
{ "#", report_ngc },
{ "H", home_all },
{ "HX", home_x },
{ "HY", home_y },
{ "HZ", home_z },
{ "HA", home_a },
{ "HB", home_b },
{ "HC", home_c },
{ "SLP", sleep },
{ "I", report_build_info },
{ "N", report_startup_lines },
};
// normalize_key puts a key string into canonical form -
// upper case without whitespace.
// start points to a null-terminated string.
// Returns the first substring that does not contain whitespace,
// converted to upper case.
char *normalize_key(char *start) {
char c;
// In the usual case, this loop will exit on the very first test,
// because the first character is likely to be non-white.
// Null ('\0') is not considered to be a space character.
while (isspace(c = *start) && c != '\0') {
++start;
}
// start now points to either a printable character or end of string
if (c == '\0') {
return start;
}
// Having found the beginning of the printable string,
// we now scan forward, converting lower to upper case,
// until we find a space character.
char *end;
for (end = start; (c = *end) != '\0' && !isspace(c); end++) {
if (islower(c)) {
*end = toupper(c);
}
}
// end now points to either a whitespace character of end of string
// In either case it is okay to place a null there
*end = '\0';
return start;
}
// This is for changing settings with $key=value .
// Lookup key in the numberedSettings map. If found, execute
// the corresponding object's "set" method. Otherwise fail.
err_t do_setting(const char *key, char *value, uint8_t client) {
// First search this numberedSettings array - aliases
// for the underlying named settings.
map<const char*, Setting*>::iterator it = numberedSettings.find(key);
if (it != numberedSettings.end()) {
return it->second->setStringValue(value);
}
// Then search the list of named settings.
for (Setting *s = SettingsList; s; s = s->link) {
if (strcmp(s->getName(), key) == 0) {
return s->setStringValue(value);
}
}
return STATUS_INVALID_STATEMENT;
}
// This is for bare commands like "$RST" - no equals sign.
// Lookup key in the dollarCommands map. If found, execute
// the corresponding command.
// As an enhancement to Classic GRBL, if the key is not found
// in the commands map, look it up in the lists of settings
// and display the current value.
err_t do_command(const char *key, uint8_t client) {
map<const char*, Command_t>::iterator i = dollarCommands.find(key);
if (i != dollarCommands.end()) {
return i->second(client);
}
// Enhancement - not in Classic GRBL:
// If it is not a command, look up the key
// as a setting and display the value.
map<const char *, Setting*>::iterator it = numberedSettings.find(key);
if (it != numberedSettings.end()) {
Setting *s = it->second;
cout << key << "=" << s->getStringValue() << '\n';
return STATUS_OK;
}
for (Setting *s = SettingsList; s; s = s->link) {
// if (s->getName().compare(k)) {
if (strcmp(s->getName(), key) == 0) {
cout << key << "=" << s->getStringValue() << '\n';
return STATUS_OK;
}
}
return STATUS_INVALID_STATEMENT;
}
uint8_t system_execute_line(char* line, uint8_t client) {
char *value = strchr(line, '=');
if (value) {
// Equals was found; replace it with null and skip it
*value++ = '\0';
do_setting(normalize_key(line), value, client);
} else {
// No equals, so it must be a command
do_command(normalize_key(line), client);
}
}
void list_settings()
{
for (Setting *s = SettingsList; s; s = s->link) {
cout << s->getName() << " " << s->getStringValue() << '\n';
}
}
void list_numbered_settings()
{
for (map<const char*, Setting*>::iterator it = numberedSettings.begin();
it != numberedSettings.end();
it++) {
cout << it->first << ": " << it->second->getStringValue() << '\n';
}
}
int main()
{
list_settings();
}