diff --git a/ESPWebDAV.cpp b/ESPWebDAV.cpp index 3205680..7db9aa9 100644 --- a/ESPWebDAV.cpp +++ b/ESPWebDAV.cpp @@ -24,7 +24,18 @@ bool ESPWebDAV::init(int chipSelectPin, SPISettings spiSettings, int serverPort) return sd.begin(chipSelectPin, spiSettings); } +// ------------------------ +bool ESPWebDAV::initSD(int chipSelectPin, SPISettings spiSettings) { + // initialize the SD card + return sd.begin(chipSelectPin, spiSettings); +} +// ------------------------ +bool ESPWebDAV::startServer() { +// ------------------------ + // start the wifi server + server->begin(); +} // ------------------------ void ESPWebDAV::handleNotFound() { diff --git a/ESPWebDAV.h b/ESPWebDAV.h index 5f02950..983c4cc 100644 --- a/ESPWebDAV.h +++ b/ESPWebDAV.h @@ -25,6 +25,8 @@ enum DepthType { DEPTH_NONE, DEPTH_CHILD, DEPTH_ALL }; class ESPWebDAV { public: bool init(int chipSelectPin, SPISettings spiSettings, int serverPort); + bool initSD(int chipSelectPin, SPISettings spiSettings); + bool startServer(); bool isClientWaiting(); void handleClient(String blank = ""); void rejectClient(String rejectMessage); diff --git a/ESPWebDAV.ino b/ESPWebDAV.ino index 3449487..76352dc 100644 --- a/ESPWebDAV.ino +++ b/ESPWebDAV.ino @@ -3,6 +3,9 @@ #include "ESP8266WiFi.h" #include "ESPWebDAV.h" +#include "serial.h" +#include "parser.h" +#include "Config.h" // LED is connected to GPIO2 on this board #define INIT_LED {pinMode(2, OUTPUT);} @@ -12,6 +15,7 @@ #define HOSTNAME "FYSETC" #define SERVER_PORT 80 #define SPI_BLOCKOUT_PERIOD 20000UL +#define WIFI_CONNECT_TIMEOUT 30000UL #define SD_CS 4 #define MISO 12 @@ -19,22 +23,76 @@ #define SCLK 14 #define CS_SENSE 5 - -const char *ssid = "507-asus"; -const char *password = "!Umv870q"; - ESPWebDAV dav; String statusMessage; bool initFailed = false; volatile long spiBlockoutTime = 0; bool weHaveBus = false; +bool wifiConnected = false; +bool wifiConnect(const char*ssid,const char*password) { + if(ssid == NULL || password==NULL) { + SERIAL_ECHOLN("Please set the wifi ssid and password first"); + } + // + wifiConnected = false; + + // Set hostname first + WiFi.hostname(HOSTNAME); + // Reduce startup surge current + WiFi.setAutoConnect(false); + WiFi.mode(WIFI_STA); + WiFi.setPhyMode(WIFI_PHY_MODE_11N); + WiFi.begin(ssid, password); + + // Wait for connection + unsigned long timeout = millis(); + while(WiFi.status() != WL_CONNECTED) { + blink(); + DBG_PRINT("."); + if(millis() > timeout + WIFI_CONNECT_TIMEOUT) + return false; + } + + DBG_PRINTLN(""); + DBG_PRINT("Connected to "); DBG_PRINTLN(ssid); + DBG_PRINT("IP address: "); DBG_PRINTLN(WiFi.localIP()); + DBG_PRINT("RSSI: "); DBG_PRINTLN(WiFi.RSSI()); + DBG_PRINT("Mode: "); DBG_PRINTLN(WiFi.getPhyMode()); + + wifiConnected = true; + + config.save(ssid,password); + + return true; +} + +void startDAVServer() { + // Check to see if other master is using the SPI bus + while(millis() < spiBlockoutTime) + blink(); + + takeBusControl(); + + // start the SD DAV server + if(!dav.init(SD_CS, SPI_FULL_SPEED, SERVER_PORT)) { + statusMessage = "Failed to initialize SD Card"; + DBG_PRINT("ERROR: "); DBG_PRINTLN(statusMessage); + // indicate error on LED + errorBlink(); + initFailed = true; + } + else + blink(); + + relenquishBusControl(); + DBG_PRINTLN("WebDAV server started"); +} // ------------------------ void setup() { -// ------------------------ // ----- GPIO ------- // Detect when other master uses SPI bus pinMode(CS_SENSE, INPUT); @@ -52,50 +110,203 @@ void setup() { delay(SPI_BLOCKOUT_PERIOD); // ----- WIFI ------- - // Set hostname first - WiFi.hostname(HOSTNAME); - // Reduce startup surge current - WiFi.setAutoConnect(false); - WiFi.mode(WIFI_STA); - WiFi.setPhyMode(WIFI_PHY_MODE_11N); - WiFi.begin(ssid, password); - - // Wait for connection - while(WiFi.status() != WL_CONNECTED) { - blink(); - DBG_PRINT("."); - } - - DBG_PRINTLN(""); - DBG_PRINT("Connected to "); DBG_PRINTLN(ssid); - DBG_PRINT("IP address: "); DBG_PRINTLN(WiFi.localIP()); - DBG_PRINT("RSSI: "); DBG_PRINTLN(WiFi.RSSI()); - DBG_PRINT("Mode: "); DBG_PRINTLN(WiFi.getPhyMode()); - - - // ----- SD Card and Server ------- - // Check to see if other master is using the SPI bus - while(millis() < spiBlockoutTime) - blink(); - - takeBusControl(); - - // start the SD DAV server - if(!dav.init(SD_CS, SPI_FULL_SPEED, SERVER_PORT)) { - statusMessage = "Failed to initialize SD Card"; - DBG_PRINT("ERROR: "); DBG_PRINTLN(statusMessage); - // indicate error on LED - errorBlink(); - initFailed = true; - } - else - blink(); - - relenquishBusControl(); - DBG_PRINTLN("WebDAV server started"); + if(config.load() == 1) { // Connected before + if(wifiConnect(config.ssid(), config.password())) { + startDAVServer(); + } + else { + SERIAL_ECHOLN("Connect fail, please set the wifi config and connect again"); + } + } + else { + SERIAL_ECHOLN("Please set the wifi ssid with M50 and password with M51 , and start connection with M52"); + } } +#define MAX_CMD_SIZE 96 +#define BUFSIZE 4 +/** + * GCode Command Queue + * A simple ring buffer of BUFSIZE command strings. + * + * Commands are copied into this buffer by the command injectors + * (immediate, serial, sd card) and they are processed sequentially by + * the main loop. The process_next_command function parses the next + * command and hands off execution to individual handler functions. + */ +uint8_t commands_in_queue = 0, // Count of commands in the queue + cmd_queue_index_r = 0, // Ring buffer read (out) position + cmd_queue_index_w = 0; // Ring buffer write (in) position + +uint32_t command_sd_pos[BUFSIZE]; +volatile uint32_t current_command_sd_pos; + +char command_queue[BUFSIZE][MAX_CMD_SIZE]; + +static bool send_ok[BUFSIZE]; + +// Number of characters read in the current line of serial input +static int serial_count; // = 0; + +/** + * Once a new command is in the ring buffer, call this to commit it + */ +inline void _commit_command(bool say_ok) { + send_ok[cmd_queue_index_w] = say_ok; + if (++cmd_queue_index_w >= BUFSIZE) cmd_queue_index_w = 0; + commands_in_queue++; +} + +/** + * Copy a command from RAM into the main command buffer. + * Return true if the command was successfully added. + * Return false for a full buffer, or if the 'command' is a comment. + */ +inline bool _enqueuecommand(const char* cmd, bool say_ok=false) { + if (*cmd == ';' || commands_in_queue >= BUFSIZE) return false; + strcpy(command_queue[cmd_queue_index_w], cmd); + _commit_command(say_ok); + return true; +} + +/** + * Get all commands waiting on the serial port and queue them. + * Exit when the buffer is full or when no more characters are + * left on the serial port. + */ +void get_serial_commands() { + static char serial_line_buffer[MAX_CMD_SIZE]; + static bool serial_comment_mode = false; + + /** + * Loop while serial characters are incoming and the queue is not full + */ + int c; + while (commands_in_queue < BUFSIZE && (c = Serial.read()) >= 0) { + + char serial_char = c; + + /** + * If the character ends the line + */ + if (serial_char == '\n' || serial_char == '\r') { + + serial_comment_mode = false; // end of line == end of comment + + // Skip empty lines and comments + if (!serial_count) { continue; } + + serial_line_buffer[serial_count] = 0; // Terminate string + serial_count = 0; // Reset buffer + + char* command = serial_line_buffer; + + while (*command == ' ') command++; // Skip leading spaces + + // Add the command to the queue + _enqueuecommand(serial_line_buffer, true); + } + else if (serial_count >= MAX_CMD_SIZE - 1) { + // Keep fetching, but ignore normal characters beyond the max length + // The command will be injected when EOL is reached + } + else if (serial_char == '\\') { // Handle escapes + if ((c = MYSERIAL0.read()) >= 0 && !serial_comment_mode) // if we have one more character, copy it over + serial_line_buffer[serial_count++] = (char)c; + // otherwise do nothing + } + else { // it's not a newline, carriage return or escape char + if (serial_char == ';') serial_comment_mode = true; + if (!serial_comment_mode) serial_line_buffer[serial_count++] = serial_char; + } + } // queue has space, serial has data +} + +/** + * M50: Set the Wifi ssid + */ +inline void gcode_M50() { + for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0'; + config.ssid(parser.string_arg); + SERIAL_ECHO(config.ssid()); +} + +/** + * M50: Set the Wifi password + */ +inline void gcode_M51() { + for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0'; + config.password(parser.string_arg); + SERIAL_ECHO(config.password()); +} + +/** + * M52: Connect the wifi + */ +inline void gcode_M52() { + if(wifiConnect(config.ssid(), config.password())) { + startDAVServer(); + } + else { + SERIAL_ECHOLN("Connect fail, please set the wifi config and connect again"); + } +} + +/** + * M53: Check wifi status + */ +inline void gcode_M53() { + if(WiFi.status() != WL_CONNECTED) { + SERIAL_ECHOLN("Wifi not connected"); + SERIAL_ECHOLN("Please set the wifi ssid with M50 and password with M51 , and start connection with M52"); + } + else { + DBG_PRINTLN(""); + DBG_PRINT("Connected to "); SERIAL_ECHOLN(WiFi.SSID()); + DBG_PRINT("IP address: "); DBG_PRINTLN(WiFi.localIP()); + DBG_PRINT("RSSI: "); DBG_PRINTLN(WiFi.RSSI()); + DBG_PRINT("Mode: "); DBG_PRINTLN(WiFi.getPhyMode()); + } +} + +/** + * Process the parsed command and dispatch it to its handler + */ +void process_parsed_command() { + //SERIAL_ECHOLNPAIR("command_letter:", parser.command_letter); + //SERIAL_ECHOLNPAIR("codenum:", parser.codenum); + + // Handle a known G, M, or T + switch (parser.command_letter) { + case 'G': switch (parser.codenum) { + default: parser.unknown_command_error(); + } + break; + + case 'M': switch (parser.codenum) { + case 50: gcode_M50(); break; + case 51: gcode_M51(); break; + case 52: gcode_M52(); break; + case 53: gcode_M53(); break; + default: parser.unknown_command_error(); + } + break; + + default: parser.unknown_command_error(); + } + + SERIAL_ECHOLN("ok"); +} + +void process_next_command() { + char * const current_command = command_queue[cmd_queue_index_r]; + current_command_sd_pos = command_sd_pos[cmd_queue_index_r]; + + // Parse the next command in the queue + parser.parse(current_command); + process_parsed_command(); +} // ------------------------ void loop() { @@ -104,19 +315,37 @@ void loop() { blink(); // do it only if there is a need to read FS - if(dav.isClientWaiting()) { - if(initFailed) - return dav.rejectClient(statusMessage); - - // has other master been using the bus in last few seconds - if(millis() < spiBlockoutTime) - return dav.rejectClient("Marlin is reading from SD card"); - - // a client is waiting and FS is ready and other SPI master is not using the bus - takeBusControl(); - dav.handleClient(); - relenquishBusControl(); + if(wifiConnected) { + if(dav.isClientWaiting()) { + if(initFailed) + return dav.rejectClient(statusMessage); + + // has other master been using the bus in last few seconds + if(millis() < spiBlockoutTime) + return dav.rejectClient("Marlin is reading from SD card"); + + // a client is waiting and FS is ready and other SPI master is not using the bus + takeBusControl(); + dav.handleClient(); + relenquishBusControl(); + } } + + // Get the serial input + if (commands_in_queue < BUFSIZE) get_serial_commands(); + + if (commands_in_queue) { + process_next_command(); + + // The queue may be reset by a command handler or by code invoked by idle() within a handler + if (commands_in_queue) { + --commands_in_queue; + if (++cmd_queue_index_r >= BUFSIZE) cmd_queue_index_r = 0; + } + } + + // + statusBlink(); } @@ -170,5 +399,17 @@ void errorBlink() { } } - - +void statusBlink() { + static unsigned long time = 0; + if(millis() > time + 1000 ) { + if(wifiConnected) { + LED_ON; + delay(50); + LED_OFF; + } + else { + LED_OFF; + } + time = millis(); + } +} diff --git a/config.cpp b/config.cpp new file mode 100644 index 0000000..56e728b --- /dev/null +++ b/config.cpp @@ -0,0 +1,51 @@ +#include +#include "config.h" + +// ------------------------ +unsigned char Config::load() { + EEPROM.begin(EEPROM_SIZE); + uint8_t *p = (uint8_t*)(&data); + for (int i = 0; i < sizeof(data); i++) + { + *(p + i) = EEPROM.read(i); + } + EEPROM.commit(); + + return data.flag; +} + +char* Config::ssid() { + return data.ssid; +} + +void Config::ssid(char* ssid) { + if(ssid == NULL) return; + strncpy(data.ssid,ssid,WIFI_SSID_LEN); +} + +char* Config::password() { + return data.psw; +} + +void Config::password(char* password) { + if(password == NULL) return; + strncpy(data.psw,password,WIFI_PASSWD_LEN); +} + +void Config::save(const char*ssid,const char*password) { + if(ssid ==NULL || password==NULL) + return; + + EEPROM.begin(EEPROM_SIZE); + data.flag = 1; + strncpy(data.ssid, ssid, WIFI_SSID_LEN); + strncpy(data.psw, password, WIFI_PASSWD_LEN); + uint8_t *p = (uint8_t*)(&data); + for (int i = 0; i < sizeof(data); i++) + { + EEPROM.write(i, *(p + i)); + } + EEPROM.commit(); +} + +Config config; diff --git a/config.h b/config.h new file mode 100644 index 0000000..12593a9 --- /dev/null +++ b/config.h @@ -0,0 +1,34 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include +#include + +#define WIFI_SSID_LEN 32 +#define WIFI_PASSWD_LEN 64 + +#define EEPROM_SIZE 512 + +typedef struct config_type +{ + unsigned char flag; // Was saved before? + char ssid[32]; + char psw[64]; +}CONFIG_TYPE; + +class Config { +public: + unsigned char load(); + char* ssid(); + void ssid(char* ssid); + char* password(); + void password(char* password); + void save(const char*ssid,const char*password); + +protected: + CONFIG_TYPE data; +}; + +extern Config config; + +#endif diff --git a/macros.h b/macros.h new file mode 100644 index 0000000..1d7d6bb --- /dev/null +++ b/macros.h @@ -0,0 +1,11 @@ +typedef uint32_t millis_t; + +#define WITHIN(V,L,H) ((V) >= (L) && (V) <= (H)) +#define NUMERIC(a) WITHIN(a, '0', '9') +#define DECIMAL(a) (NUMERIC(a) || a == '.') +#define NUMERIC_SIGNED(a) (NUMERIC(a) || (a) == '-' || (a) == '+') +#define DECIMAL_SIGNED(a) (DECIMAL(a) || (a) == '-' || (a) == '+') + +#define constrain(value, arg_min, arg_max) ((value) < (arg_min) ? (arg_min) :((value) > (arg_max) ? (arg_max) : (value))) + +#define FORCE_INLINE __attribute__((always_inline)) inline diff --git a/parser.cpp b/parser.cpp new file mode 100644 index 0000000..e17ca6e --- /dev/null +++ b/parser.cpp @@ -0,0 +1,200 @@ +/** + * Base on Malrin https://github.com/MarlinFirmware/Marlin + * parser.cpp - Parser for a GCode line, providing a parameter interface. + */ +#include "serial.h" +#include "parser.h" + +// Must be declared for allocation and to satisfy the linker +// Zero values need no initialization. +char *GCodeParser::command_ptr, + *GCodeParser::string_arg, + *GCodeParser::value_ptr; +char GCodeParser::command_letter; +int GCodeParser::codenum; + +char *GCodeParser::command_args; // start of parameters + +// Create a global instance of the GCode parser singleton +GCodeParser parser; + +/** + * Clear all code-seen (and value pointers) + * + * Since each param is set/cleared on seen codes, + * this may be optimized by commenting out ZERO(param) + */ +void GCodeParser::reset() { + string_arg = NULL; // No whole line argument + command_letter = '?'; // No command letter + codenum = 0; // No command code +} + +// Populate all fields by parsing a single line of GCode +// 58 bytes of SRAM are used to speed up seen/value +void GCodeParser::parse(char *p) { + + reset(); // No codes to report + + // Skip spaces + while (*p == ' ') ++p; + + // Skip N[-0-9] if included in the command line + if (*p == 'N' && NUMERIC_SIGNED(p[1])) { + p += 2; // skip N[-0-9] + while (NUMERIC(*p)) ++p; // skip [0-9]* + while (*p == ' ') ++p; // skip [ ]* + } + + // *p now points to the current command, which should be G, M, or T + command_ptr = p; + + // Get the command letter, which must be G, M, or T + const char letter = *p++; + + // Nullify asterisk and trailing whitespace + char *starpos = strchr(p, '*'); + if (starpos) { + --starpos; // * + while (*starpos == ' ') --starpos; // spaces... + starpos[1] = '\0'; + } + + // Bail if the letter is not G, M, or T + switch (letter) { case 'G': case 'M': case 'T': break; default: return; } + + // Skip spaces to get the numeric part + while (*p == ' ') p++; + + // Bail if there's no command code number + if (!NUMERIC(*p)) return; + + // Save the command letter at this point + // A '?' signifies an unknown command + command_letter = letter; + + // Get the code number - integer digits only + codenum = 0; + do { + codenum *= 10, codenum += *p++ - '0'; + } while (NUMERIC(*p)); + + // Skip all spaces to get to the first argument, or nul + while (*p == ' ') p++; + + // The command parameters (if any) start here, for sure! + + // Only use string_arg for these M codes + if (letter == 'M') switch (codenum) { case 50: case 51: case 23: case 28: case 30: case 117: case 118: case 928: string_arg = p; return; default: break; } + + #if defined(DEBUG_GCODE_PARSER) + const bool debug = codenum == 800; + #endif + + /** + * Find all parameters, set flags and pointers for fast parsing + * + * Most codes ignore 'string_arg', but those that want a string will get the right pointer. + * The following loop assigns the first "parameter" having no numeric value to 'string_arg'. + * This allows M0/M1 with expire time to work: "M0 S5 You Win!" + * For 'M118' you must use 'E1' and 'A1' rather than just 'E' or 'A' + */ + string_arg = NULL; + while (const char code = *p++) { // Get the next parameter. A NUL ends the loop + + // Special handling for M32 [P] !/path/to/file.g# + // The path must be the last parameter + if (code == '!' && letter == 'M' && codenum == 32) { + string_arg = p; // Name starts after '!' + char * const lb = strchr(p, '#'); // Already seen '#' as SD char (to pause buffering) + if (lb) *lb = '\0'; // Safe to mark the end of the filename + return; + } + + // Arguments MUST be uppercase for fast GCode parsing + #define PARAM_TEST true + + if (PARAM_TEST) { + + while (*p == ' ') p++; // Skip spaces between parameters & values + + const bool has_num = valid_float(p); + + #if defined(DEBUG_GCODE_PARSER) + if (debug) { + SERIAL_ECHOPAIR("Got letter ", code); + SERIAL_ECHOPAIR(" at index ", (int)(p - command_ptr - 1)); + if (has_num) SERIAL_ECHOPGM(" (has_num)"); + } + #endif + + if (!has_num && !string_arg) { // No value? First time, keep as string_arg + string_arg = p - 1; + #if defined(DEBUG_GCODE_PARSER) + if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG + #endif + } + + #if defined(DEBUG_GCODE_PARSER) + if (debug) SERIAL_EOL(); + #endif + } + else if (!string_arg) { // Not A-Z? First time, keep as the string_arg + string_arg = p - 1; + #if defined(DEBUG_GCODE_PARSER) + if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG + #endif + } + + if (!WITHIN(*p, 'A', 'Z')) { // Another parameter right away? + while (*p && DECIMAL_SIGNED(*p)) p++; // Skip over the value section of a parameter + while (*p == ' ') p++; // Skip over all spaces + } + } +} + +void GCodeParser::unknown_command_error() { + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR("Unknown:", command_ptr); + SERIAL_CHAR('"'); + SERIAL_EOL(); +} + +#if defined(DEBUG_GCODE_PARSER) + + void GCodeParser::debug() { + SERIAL_ECHOPAIR("Command: ", command_ptr); + SERIAL_ECHOPAIR(" (", command_letter); + SERIAL_ECHO(codenum); + SERIAL_ECHOLNPGM(")"); + SERIAL_ECHOPAIR(" args: \"", command_args); + SERIAL_CHAR('"'); + if (string_arg) { + SERIAL_ECHOPGM(" string: \""); + SERIAL_ECHO(string_arg); + SERIAL_CHAR('"'); + } + SERIAL_ECHOPGM("\n\n"); + for (char c = 'A'; c <= 'Z'; ++c) { + if (seen(c)) { + SERIAL_ECHOPAIR("Code '", c); SERIAL_ECHOPGM("':"); + if (has_value()) { + SERIAL_ECHOPAIR("\n float: ", value_float()); + SERIAL_ECHOPAIR("\n long: ", value_long()); + SERIAL_ECHOPAIR("\n ulong: ", value_ulong()); + SERIAL_ECHOPAIR("\n millis: ", value_millis()); + SERIAL_ECHOPAIR("\n sec-ms: ", value_millis_from_seconds()); + SERIAL_ECHOPAIR("\n int: ", value_int()); + SERIAL_ECHOPAIR("\n ushort: ", value_ushort()); + SERIAL_ECHOPAIR("\n byte: ", (int)value_byte()); + SERIAL_ECHOPAIR("\n bool: ", (int)value_bool()); + SERIAL_ECHOPAIR("\n linear: ", value_linear_units()); + } + else + SERIAL_ECHOPGM(" (no value)"); + SERIAL_ECHOPGM("\n\n"); + } + } + } + +#endif // DEBUG_GCODE_PARSER diff --git a/parser.h b/parser.h new file mode 100644 index 0000000..684cca2 --- /dev/null +++ b/parser.h @@ -0,0 +1,140 @@ +/** + * Base on Malrin https://github.com/MarlinFirmware/Marlin + * parser.h - Parser for a GCode line, providing a parameter interface. + * Codes like M149 control the way the GCode parser behaves, + * so settings for these codes are located in this class. + */ + +#ifndef _PARSER_H_ +#define _PARSER_H_ + +//#include "enum.h" +#include +#include "macros.h" + +#define strtof strtod + +/** + * GCode parser + * + * - Parse a single gcode line for its letter, code, subcode, and parameters + * - Provide accessors for parameters: + * - Parameter exists + * - Parameter has value + * - Parameter value in different units and types + */ +class GCodeParser { + +private: + static char *value_ptr; // Set by seen, used to fetch the value + static char *command_args; // Args start here, for slow scan + +public: + + // Command line state + static char *command_ptr, // The command, so it can be echoed + *string_arg; // string of command line + + static char command_letter; // G, M, or T + static int codenum; // 123 + + #if defined(DEBUG_GCODE_PARSER) + static void debug(); + #endif + + GCodeParser() { + } + + // Reset is done before parsing + static void reset(); + + #define LETTER_BIT(N) ((N) - 'A') + + FORCE_INLINE static bool valid_signless(const char * const p) { + return NUMERIC(p[0]) || (p[0] == '.' && NUMERIC(p[1])); // .?[0-9] + } + + FORCE_INLINE static bool valid_float(const char * const p) { + return valid_signless(p) || ((p[0] == '-' || p[0] == '+') && valid_signless(&p[1])); // [-+]?.?[0-9] + } + + + // Code is found in the string. If not found, value_ptr is unchanged. + // This allows "if (seen('A')||seen('B'))" to use the last-found value. + static bool seen(const char c) { + char *p = strchr(command_args, c); + const bool b = !!p; + if (b) value_ptr = valid_float(&p[1]) ? &p[1] : (char*)NULL; + return b; + } + + static bool seen_any() { return *command_args == '\0'; } + + #define SEEN_TEST(L) !!strchr(command_args, L) + + // Seen any axis parameter + static bool seen_axis() { + return SEEN_TEST('X') || SEEN_TEST('Y') || SEEN_TEST('Z') || SEEN_TEST('E'); + } + + // Populate all fields by parsing a single line of GCode + // This uses 54 bytes of SRAM to speed up seen/value + static void parse(char * p); + + // The code value pointer was set + FORCE_INLINE static bool has_value() { return value_ptr != NULL; } + + // Seen a parameter with a value + inline static bool seenval(const char c) { return seen(c) && has_value(); } + + // Float removes 'E' to prevent scientific notation interpretation + inline static float value_float() { + if (value_ptr) { + char *e = value_ptr; + for (;;) { + const char c = *e; + if (c == '\0' || c == ' ') break; + if (c == 'E' || c == 'e') { + *e = '\0'; + const float ret = strtof(value_ptr, NULL); + *e = c; + return ret; + } + ++e; + } + return strtof(value_ptr, NULL); + } + return 0; + } + + // Code value as a long or ulong + inline static int32_t value_long() { return value_ptr ? strtol(value_ptr, NULL, 10) : 0L; } + inline static uint32_t value_ulong() { return value_ptr ? strtoul(value_ptr, NULL, 10) : 0UL; } + + // Code value for use as time + FORCE_INLINE static millis_t value_millis() { return value_ulong(); } + FORCE_INLINE static millis_t value_millis_from_seconds() { return value_float() * 1000UL; } + + // Reduce to fewer bits + FORCE_INLINE static int16_t value_int() { return (int16_t)value_long(); } + FORCE_INLINE static uint16_t value_ushort() { return (uint16_t)value_long(); } + inline static uint8_t value_byte() { return (uint8_t)constrain(value_long(), 0, 255); } + + // Bool is true with no value or non-zero + inline static bool value_bool() { return !has_value() || !!value_byte(); } + + void unknown_command_error(); + + // Provide simple value accessors with default option + FORCE_INLINE static float floatval(const char c, const float dval=0.0) { return seenval(c) ? value_float() : dval; } + FORCE_INLINE static bool boolval(const char c, const bool dval=false) { return seenval(c) ? value_bool() : (seen(c) ? true : dval); } + FORCE_INLINE static uint8_t byteval(const char c, const uint8_t dval=0) { return seenval(c) ? value_byte() : dval; } + FORCE_INLINE static int16_t intval(const char c, const int16_t dval=0) { return seenval(c) ? value_int() : dval; } + FORCE_INLINE static uint16_t ushortval(const char c, const uint16_t dval=0) { return seenval(c) ? value_ushort() : dval; } + FORCE_INLINE static int32_t longval(const char c, const int32_t dval=0) { return seenval(c) ? value_long() : dval; } + FORCE_INLINE static uint32_t ulongval(const char c, const uint32_t dval=0) { return seenval(c) ? value_ulong() : dval; } +}; + +extern GCodeParser parser; + +#endif // _PARSER_H_ diff --git a/serial.cpp b/serial.cpp new file mode 100644 index 0000000..791e8fa --- /dev/null +++ b/serial.cpp @@ -0,0 +1,36 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program 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. + * + * This program 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 this program. If not, see . + * + */ + +#include "serial.h" + +const char errormagic[] PROGMEM = "Error:"; +const char echomagic[] PROGMEM = "echo:"; + +void serial_echopair_PGM(const char* s_P, const char *v) { serialprintPGM(s_P); SERIAL_ECHO(v); } +void serial_echopair_PGM(const char* s_P, char v) { serialprintPGM(s_P); SERIAL_CHAR(v); } +void serial_echopair_PGM(const char* s_P, int v) { serialprintPGM(s_P); SERIAL_ECHO(v); } +void serial_echopair_PGM(const char* s_P, long v) { serialprintPGM(s_P); SERIAL_ECHO(v); } +void serial_echopair_PGM(const char* s_P, float v) { serialprintPGM(s_P); SERIAL_ECHO(v); } +void serial_echopair_PGM(const char* s_P, double v) { serialprintPGM(s_P); SERIAL_ECHO(v); } +void serial_echopair_PGM(const char* s_P, unsigned long v) { serialprintPGM(s_P); SERIAL_ECHO(v); } + +void serial_spaces(uint8_t count) { while (count--) SERIAL_CHAR(' '); } diff --git a/serial.h b/serial.h new file mode 100644 index 0000000..f05c069 --- /dev/null +++ b/serial.h @@ -0,0 +1,98 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program 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. + * + * This program 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 this program. If not, see . + * + */ + +#ifndef __SERIAL_H__ +#define __SERIAL_H__ + +#include +#include "macros.h" + +#define MYSERIAL0 Serial + +extern const char echomagic[] PROGMEM; +extern const char errormagic[] PROGMEM; + +#define SERIAL_CHAR(x) ((void)MYSERIAL0.write(x)) +#define SERIAL_EOL() SERIAL_CHAR('\n') + +#define SERIAL_PRINT(x,b) MYSERIAL0.print(x,b) +#define SERIAL_PRINTLN(x,b) MYSERIAL0.println(x,b) +#define SERIAL_PRINTF(args...) MYSERIAL0.printf(args) + +#define SERIAL_FLUSH() MYSERIAL0.flush() + +#define SERIAL_PROTOCOLCHAR(x) SERIAL_CHAR(x) +#define SERIAL_PROTOCOL(x) MYSERIAL0.print(x) +#define SERIAL_PROTOCOL_F(x,y) MYSERIAL0.print(x,y) +#define SERIAL_PROTOCOLPGM(x) serialprintPGM(PSTR(x)) +#define SERIAL_PROTOCOLLN(x) do{ MYSERIAL0.print(x); SERIAL_EOL(); }while(0) +#define SERIAL_PROTOCOLLNPGM(x) serialprintPGM(PSTR(x "\n")) +#define SERIAL_PROTOCOLPAIR(name, value) serial_echopair_PGM(PSTR(name),(value)) +#define SERIAL_PROTOCOLLNPAIR(name, value) do{ SERIAL_PROTOCOLPAIR(name, value); SERIAL_EOL(); }while(0) + +#define SERIAL_ECHO_START() serialprintPGM(echomagic) +#define SERIAL_ECHO(x) SERIAL_PROTOCOL(x) +#define SERIAL_ECHOPGM(x) SERIAL_PROTOCOLPGM(x) +#define SERIAL_ECHOLN(x) SERIAL_PROTOCOLLN(x) +#define SERIAL_ECHOLNPGM(x) SERIAL_PROTOCOLLNPGM(x) +#define SERIAL_ECHOPAIR(pre,value) SERIAL_PROTOCOLPAIR(pre, value) +#define SERIAL_ECHOLNPAIR(pre,value) SERIAL_PROTOCOLLNPAIR(pre, value) +#define SERIAL_ECHO_F(x,y) SERIAL_PROTOCOL_F(x,y) + +#define SERIAL_ERROR_START() serialprintPGM(errormagic) +#define SERIAL_ERROR(x) SERIAL_PROTOCOL(x) +#define SERIAL_ERRORPGM(x) SERIAL_PROTOCOLPGM(x) +#define SERIAL_ERRORLN(x) SERIAL_PROTOCOLLN(x) +#define SERIAL_ERRORLNPGM(x) SERIAL_PROTOCOLLNPGM(x) + +// These macros compensate for float imprecision +#define SERIAL_PROTOCOLPAIR_F(pre, value) SERIAL_PROTOCOLPAIR(pre, FIXFLOAT(value)) +#define SERIAL_PROTOCOLLNPAIR_F(pre, value) SERIAL_PROTOCOLLNPAIR(pre, FIXFLOAT(value)) +#define SERIAL_ECHOPAIR_F(pre,value) SERIAL_ECHOPAIR(pre, FIXFLOAT(value)) +#define SERIAL_ECHOLNPAIR_F(pre, value) SERIAL_ECHOLNPAIR(pre, FIXFLOAT(value)) + +// +// Functions for serial printing from PROGMEM. (Saves loads of SRAM.) +// +FORCE_INLINE void serialprintPGM(const char* str) { + while (char ch = pgm_read_byte(str++)) SERIAL_CHAR(ch); +} + +void serial_echopair_PGM(const char* s_P, const char *v); +void serial_echopair_PGM(const char* s_P, char v); +void serial_echopair_PGM(const char* s_P, int v); +void serial_echopair_PGM(const char* s_P, long v); +void serial_echopair_PGM(const char* s_P, float v); +void serial_echopair_PGM(const char* s_P, double v); +void serial_echopair_PGM(const char* s_P, unsigned int v); +void serial_echopair_PGM(const char* s_P, unsigned long v); +FORCE_INLINE void serial_echopair_PGM(const char* s_P, uint8_t v) { serial_echopair_PGM(s_P, (int)v); } +FORCE_INLINE void serial_echopair_PGM(const char* s_P, uint16_t v) { serial_echopair_PGM(s_P, (int)v); } +FORCE_INLINE void serial_echopair_PGM(const char* s_P, bool v) { serial_echopair_PGM(s_P, (int)v); } +FORCE_INLINE void serial_echopair_PGM(const char* s_P, void *v) { serial_echopair_PGM(s_P, (unsigned long)v); } + +void serial_spaces(uint8_t count); +#define SERIAL_ECHO_SP(C) serial_spaces(C) +#define SERIAL_ERROR_SP(C) serial_spaces(C) +#define SERIAL_PROTOCOL_SP(C) serial_spaces(C) + +#endif // __SERIAL_H__