diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index 8a490b22..288d67f1 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -61,6 +61,7 @@ #include "language.h" #include "pins_arduino.h" #include "math.h" +#include "util.h" #ifdef BLINKM #include "BlinkM.h" @@ -1227,6 +1228,7 @@ static inline bool code_seen(const char *code) { return (strchr_pointer = str static inline float code_value() { return strtod(strchr_pointer+1, NULL); } static inline long code_value_long() { return strtol(strchr_pointer+1, NULL, 10); } static inline int16_t code_value_short() { return int16_t(strtol(strchr_pointer+1, NULL, 10)); }; +static inline uint8_t code_value_uint8() { return uint8_t(strtol(strchr_pointer+1, NULL, 10)); }; #define DEFINE_PGM_READ_ANY(type, reader) \ static inline type pgm_read_any(const type *p) \ @@ -2766,7 +2768,7 @@ void process_commands() } break; - case 44: // M45: Reset the bed skew and offset calibration. + case 44: // M44: Prusa3D: Reset the bed skew and offset calibration. // Reset the skew and offset in both RAM and EEPROM. reset_bed_offset_and_skew(); // Reset world2machine_rotation_and_skew and world2machine_shift, therefore @@ -2775,7 +2777,7 @@ void process_commands() world2machine_revert_to_uncorrected(); break; - case 45: // M46: bed skew and offset with manual Z up + case 45: // M45: Prusa3D: bed skew and offset with manual Z up { // Disable the default update procedure of the display. We will do a modal dialog. lcd_update_enable(false); @@ -2835,7 +2837,12 @@ void process_commands() break; } -#if 1 + case 47: + // M47: Prusa3D: Show end stops dialog on the display. + lcd_diag_show_end_stops(); + break; + +#if 0 case 48: // M48: scan the bed induction sensor points, print the sensor trigger coordinates to the serial line for visualization on the PC. { // Disable the default update procedure of the display. We will do a modal dialog. @@ -2874,10 +2881,6 @@ void process_commands() } #endif - case 47: - lcd_diag_show_end_stops(); - break; - // M48 Z-Probe repeatability measurement function. // // Usage: M48 @@ -3488,7 +3491,16 @@ Sigma_Exit: } break; case 115: // M115 - SERIAL_PROTOCOLRPGM(MSG_M115_REPORT); + if (code_seen('V')) { + // Report the Prusa version number. + SERIAL_PROTOCOLLNRPGM(FW_VERSION_STR_P()); + } else if (code_seen('U')) { + // Check the firmware version provided. If the firmware version provided by the U code is higher than the currently running firmware, + // pause the print and ask the user to upgrade the firmware. + show_upgrade_dialog_if_version_newer(++ strchr_pointer); + } else { + SERIAL_PROTOCOLRPGM(MSG_M115_REPORT); + } break; case 117: // M117 display message starpos = (strchr(strchr_pointer + 5,'*')); diff --git a/Firmware/language_all.cpp b/Firmware/language_all.cpp index 65fe97dd..9be16d34 100644 --- a/Firmware/language_all.cpp +++ b/Firmware/language_all.cpp @@ -1760,6 +1760,32 @@ const char * const MSG_MOVE_Z_LANG_TABLE[LANG_NUM] PROGMEM = { MSG_MOVE_Z_PL }; +const char MSG_NEW_FIRMWARE_AVAILABLE_EN[] PROGMEM = "New firmware version available:"; +const char MSG_NEW_FIRMWARE_AVAILABLE_CZ[] PROGMEM = "Vysla nova verze firmware:"; +const char MSG_NEW_FIRMWARE_AVAILABLE_IT[] PROGMEM = "New firmware version available:"; +const char MSG_NEW_FIRMWARE_AVAILABLE_ES[] PROGMEM = "New firmware version available:"; +const char MSG_NEW_FIRMWARE_AVAILABLE_PL[] PROGMEM = "New firmware version available:"; +const char * const MSG_NEW_FIRMWARE_AVAILABLE_LANG_TABLE[LANG_NUM] PROGMEM = { + MSG_NEW_FIRMWARE_AVAILABLE_EN, + MSG_NEW_FIRMWARE_AVAILABLE_CZ, + MSG_NEW_FIRMWARE_AVAILABLE_IT, + MSG_NEW_FIRMWARE_AVAILABLE_ES, + MSG_NEW_FIRMWARE_AVAILABLE_PL +}; + +const char MSG_NEW_FIRMWARE_PLEASE_UPGRADE_EN[] PROGMEM = "Please upgrade."; +const char MSG_NEW_FIRMWARE_PLEASE_UPGRADE_CZ[] PROGMEM = "Prosim aktualizujte."; +const char MSG_NEW_FIRMWARE_PLEASE_UPGRADE_IT[] PROGMEM = "Please upgrade."; +const char MSG_NEW_FIRMWARE_PLEASE_UPGRADE_ES[] PROGMEM = "Please upgrade."; +const char MSG_NEW_FIRMWARE_PLEASE_UPGRADE_PL[] PROGMEM = "Please upgrade."; +const char * const MSG_NEW_FIRMWARE_PLEASE_UPGRADE_LANG_TABLE[LANG_NUM] PROGMEM = { + MSG_NEW_FIRMWARE_PLEASE_UPGRADE_EN, + MSG_NEW_FIRMWARE_PLEASE_UPGRADE_CZ, + MSG_NEW_FIRMWARE_PLEASE_UPGRADE_IT, + MSG_NEW_FIRMWARE_PLEASE_UPGRADE_ES, + MSG_NEW_FIRMWARE_PLEASE_UPGRADE_PL +}; + const char MSG_NO_EN[] PROGMEM = "No"; const char MSG_NO_CZ[] PROGMEM = "Ne"; const char MSG_NO_IT[] PROGMEM = "No"; diff --git a/Firmware/language_all.h b/Firmware/language_all.h index 9d679dcc..1654717c 100644 --- a/Firmware/language_all.h +++ b/Firmware/language_all.h @@ -280,6 +280,10 @@ extern const char* const MSG_MOVE_Y_LANG_TABLE[LANG_NUM]; #define MSG_MOVE_Y LANG_TABLE_SELECT(MSG_MOVE_Y_LANG_TABLE) extern const char* const MSG_MOVE_Z_LANG_TABLE[LANG_NUM]; #define MSG_MOVE_Z LANG_TABLE_SELECT(MSG_MOVE_Z_LANG_TABLE) +extern const char* const MSG_NEW_FIRMWARE_AVAILABLE_LANG_TABLE[LANG_NUM]; +#define MSG_NEW_FIRMWARE_AVAILABLE LANG_TABLE_SELECT(MSG_NEW_FIRMWARE_AVAILABLE_LANG_TABLE) +extern const char* const MSG_NEW_FIRMWARE_PLEASE_UPGRADE_LANG_TABLE[LANG_NUM]; +#define MSG_NEW_FIRMWARE_PLEASE_UPGRADE LANG_TABLE_SELECT(MSG_NEW_FIRMWARE_PLEASE_UPGRADE_LANG_TABLE) extern const char* const MSG_NO_LANG_TABLE[LANG_NUM]; #define MSG_NO LANG_TABLE_SELECT(MSG_NO_LANG_TABLE) extern const char* const MSG_NOT_COLOR_LANG_TABLE[LANG_NUM]; diff --git a/Firmware/language_cz.h b/Firmware/language_cz.h index 9ed4fea2..6d37355e 100644 --- a/Firmware/language_cz.h +++ b/Firmware/language_cz.h @@ -308,4 +308,7 @@ #define MSG_BED_LEVELING_FAILED_POINT_LOW "Kalibrace Z selhala. Sensor nesepnul. Znecistena tryska? Cekam na reset." #define MSG_BED_LEVELING_FAILED_POINT_HIGH "Kalibrace Z selhala. Sensor sepnul prilis vysoko. Cekam na reset." +#define MSG_NEW_FIRMWARE_AVAILABLE "Vysla nova verze firmware:" +#define MSG_NEW_FIRMWARE_PLEASE_UPGRADE "Prosim aktualizujte." + #endif // LANGUAGE_EN_H diff --git a/Firmware/language_en.h b/Firmware/language_en.h index 8715f855..ccb3115b 100644 --- a/Firmware/language_en.h +++ b/Firmware/language_en.h @@ -301,4 +301,7 @@ #define MSG_BED_LEVELING_FAILED_POINT_LOW "Bed leveling failed. Sensor didnt trigger. Debris on nozzle? Waiting for reset." #define MSG_BED_LEVELING_FAILED_POINT_HIGH "Bed leveling failed. Sensor triggered too high. Waiting for reset." +#define MSG_NEW_FIRMWARE_AVAILABLE "New firmware version available:" +#define MSG_NEW_FIRMWARE_PLEASE_UPGRADE "Please upgrade." + #endif // LANGUAGE_EN_H diff --git a/Firmware/ultralcd.cpp b/Firmware/ultralcd.cpp index 31f5d259..1a84ddb1 100644 --- a/Firmware/ultralcd.cpp +++ b/Firmware/ultralcd.cpp @@ -8,6 +8,8 @@ #include "stepper.h" #include "ConfigurationStore.h" #include + +#include "util.h" //#include "Configuration.h" @@ -659,7 +661,14 @@ static void lcd_support_menu() MENU_ITEM(back, MSG_MAIN, lcd_main_menu); - MENU_ITEM(back, PSTR(MSG_FW_VERSION " - " FW_version), lcd_main_menu); + // Ideally this block would be optimized out by the compiler. + const uint8_t fw_string_len = strlen_P(FW_VERSION_STR_P()); + if (fw_string_len < 6) { + MENU_ITEM(back, PSTR(MSG_FW_VERSION " - " FW_version), lcd_main_menu); + } else { + MENU_ITEM(back, PSTR("FW - " FW_version), lcd_main_menu); + } + MENU_ITEM(back, MSG_PRUSA3D, lcd_main_menu); MENU_ITEM(back, MSG_PRUSA3D_FORUM, lcd_main_menu); MENU_ITEM(back, MSG_PRUSA3D_HOWTO, lcd_main_menu); @@ -1324,6 +1333,20 @@ void lcd_show_fullscreen_message_and_wait_P(const char *msg) } } +void lcd_wait_for_click() +{ + for (;;) { + manage_heater(); + manage_inactivity(true); + if (lcd_clicked()) { + while (lcd_clicked()) ; + delay(10); + while (lcd_clicked()) ; + return; + } + } +} + int8_t lcd_show_fullscreen_message_yes_no_and_wait_P(const char *msg, bool allow_timeouting) { lcd_display_message_fullscreen_P(msg); diff --git a/Firmware/ultralcd.h b/Firmware/ultralcd.h index 42312606..cf2f43bc 100644 --- a/Firmware/ultralcd.h +++ b/Firmware/ultralcd.h @@ -39,6 +39,7 @@ static void lcd_menu_statistics(); extern void lcd_display_message_fullscreen_P(const char *msg); + extern void lcd_wait_for_click(); extern void lcd_show_fullscreen_message_and_wait_P(const char *msg); // 0: no, 1: yes, -1: timeouted extern int8_t lcd_show_fullscreen_message_yes_no_and_wait_P(const char *msg, bool allow_timeouting = true); @@ -173,6 +174,7 @@ char *ftostr52(const float &x); extern void lcd_implementation_clear(); extern void lcd_printPGM(const char* str); extern void lcd_print_at_PGM(uint8_t x, uint8_t y, const char* str); +extern void lcd_implementation_write(char c); extern void lcd_implementation_print(const char *str); extern void lcd_implementation_print(int8_t i); extern void lcd_implementation_print_at(uint8_t x, uint8_t y, int8_t i); diff --git a/Firmware/ultralcd_implementation_hitachi_HD44780.h b/Firmware/ultralcd_implementation_hitachi_HD44780.h index 94601e4a..70eae893 100644 --- a/Firmware/ultralcd_implementation_hitachi_HD44780.h +++ b/Firmware/ultralcd_implementation_hitachi_HD44780.h @@ -591,6 +591,11 @@ void lcd_print_at_PGM(uint8_t x, uint8_t y, const char* str) } } +void lcd_implementation_write(char c) +{ + lcd.write(c); +} + void lcd_implementation_print(int8_t i) { lcd.print(i); diff --git a/Firmware/util.cpp b/Firmware/util.cpp new file mode 100644 index 00000000..78349c25 --- /dev/null +++ b/Firmware/util.cpp @@ -0,0 +1,279 @@ +#include "Configuration.h" + +#include "ultralcd.h" +#include "language.h" + +// Allocate the version string in the program memory. Otherwise the string lands either on the stack or in the global RAM. +const char FW_VERSION_STR[] PROGMEM = FW_version; + +const char* FW_VERSION_STR_P() +{ + return FW_VERSION_STR; +} + +enum RevisionType +{ + REVISION_DEV = 0, + REVISION_ALPHA = 1, + REVISION_BETA = 2, + REVISION_RC, + REVISION_RC2, + REVISION_RC3, + REVISION_RC4, + REVISION_RC5, + REVISION_RELEASED = 127 +}; + +const char STR_REVISION_DEV [] PROGMEM = "dev"; +const char STR_REVISION_ALPHA[] PROGMEM = "alpha"; +const char STR_REVISION_BETA [] PROGMEM = "beta"; +const char STR_REVISION_RC [] PROGMEM = "rc"; + +inline bool is_whitespace_or_nl(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == 'r'; +} + +inline bool is_whitespace_or_nl_or_eol(char c) +{ + return c == 0 || c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +inline bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +// Parse a major.minor.revision version number. +// Return true if valid. +inline bool parse_version(const char *str, uint16_t version[4]) +{ +#if 0 + SERIAL_ECHOPGM("Parsing version string "); + SERIAL_ECHO(str); + SERIAL_ECHOLNPGM(""); +#endif + + const char *major = str; + const char *p = str; + while (is_digit(*p)) ++ p; + if (*p != '.') + return false; + const char *minor = ++ p; + while (is_digit(*p)) ++ p; + if (*p != '.') + return false; + const char *rev = ++ p; + while (is_digit(*p)) ++ p; + if (! is_whitespace_or_nl_or_eol(*p) && *p != '-') + return false; + + char *endptr = NULL; + version[0] = strtol(major, &endptr, 10); + if (endptr != minor - 1) + return false; + version[1] = strtol(minor, &endptr, 10); + if (endptr != rev - 1) + return false; + version[2] = strtol(rev, &endptr, 10); + if (endptr != p) + return false; + + version[3] = REVISION_RELEASED; + if (*p ++ == '-') { + const char *q = p; + while (! is_whitespace_or_nl_or_eol(*q)) + ++ q; + uint8_t n = q - p; + if (n == strlen_P(STR_REVISION_DEV) && strncmp_P(p, STR_REVISION_DEV, n) == 0) + version[3] = REVISION_DEV; + else if (n == strlen_P(STR_REVISION_ALPHA) && strncmp_P(p, STR_REVISION_ALPHA, n) == 0) + version[3] = REVISION_ALPHA; + else if (n == strlen_P(STR_REVISION_BETA) && strncmp_P(p, STR_REVISION_BETA, n) == 0) + version[3] = REVISION_BETA; + else if ((n == 2 || n == 3) && p[0] == 'r' && p[1] == 'c') { + if (n == 2) + version[3] = REVISION_RC; + else { + if (is_digit(p[2])) + version[3] = REVISION_RC + p[2] - '1'; + else + return false; + } + } else + return false; + } + +#if 0 + SERIAL_ECHOPGM("Version parsed, major: "); + SERIAL_ECHO(version[0]); + SERIAL_ECHOPGM(", minor: "); + SERIAL_ECHO(version[1]); + SERIAL_ECHOPGM(", revision: "); + SERIAL_ECHO(version[2]); + SERIAL_ECHOPGM(", flavor: "); + SERIAL_ECHO(version[3]); + SERIAL_ECHOLNPGM(""); +#endif + return true; +} + +inline bool strncmp_PP(const char *p1, const char *p2, uint8_t n) +{ + for (; n > 0; -- n, ++ p1, ++ p2) { + if (pgm_read_byte(p1) < pgm_read_byte(p2)) + return -1; + if (pgm_read_byte(p1) > pgm_read_byte(p2)) + return 1; + if (pgm_read_byte(p1) == 0) + return 0; + } + return 0; +} + +// Parse a major.minor.revision version number. +// Return true if valid. +inline bool parse_version_P(const char *str, uint16_t version[4]) +{ +#if 0 + SERIAL_ECHOPGM("Parsing version string "); + SERIAL_ECHORPGM(str); + SERIAL_ECHOLNPGM(""); +#endif + + const char *major = str; + const char *p = str; + while (is_digit(char(pgm_read_byte(p)))) ++ p; + if (pgm_read_byte(p) != '.') + return false; + const char *minor = ++ p; + while (is_digit(char(pgm_read_byte(p)))) ++ p; + if (pgm_read_byte(p) != '.') + return false; + const char *rev = ++ p; + while (is_digit(char(pgm_read_byte(p)))) ++ p; + if (! is_whitespace_or_nl_or_eol(char(pgm_read_byte(p))) && pgm_read_byte(p) != '-') + return false; + + char buf[5]; + uint8_t n = minor - major - 1; + if (n > 4) + return false; + memcpy_P(buf, major, n); buf[n] = 0; + char *endptr = NULL; + version[0] = strtol(buf, &endptr, 10); + if (*endptr != 0) + return false; + n = rev - minor - 1; + if (n > 4) + return false; + memcpy_P(buf, minor, n); buf[n] = 0; + version[1] = strtol(buf, &endptr, 10); + if (*endptr != 0) + return false; + n = p - rev; + if (n > 4) + return false; + memcpy_P(buf, rev, n); + buf[n] = 0; + version[2] = strtol(buf, &endptr, 10); + if (*endptr != 0) + return false; + + version[3] = REVISION_RELEASED; + if (pgm_read_byte(p ++) == '-') { + const char *q = p; + while (! is_whitespace_or_nl_or_eol(char(pgm_read_byte(q)))) + ++ q; + n = q - p; + if (n == strlen_P(STR_REVISION_DEV) && strncmp_PP(p, STR_REVISION_DEV, n) == 0) + version[3] = REVISION_DEV; + else if (n == strlen_P(STR_REVISION_ALPHA) && strncmp_PP(p, STR_REVISION_ALPHA, n) == 0) + version[3] = REVISION_ALPHA; + else if (n == strlen_P(STR_REVISION_BETA) && strncmp_PP(p, STR_REVISION_BETA, n) == 0) + version[3] = REVISION_BETA; + else if ((n == 2 || n == 3) && strncmp_PP(p, STR_REVISION_RC, 2) == 0) { + if (n == 2) + version[3] = REVISION_RC; + else { + p += 2; + if (is_digit(pgm_read_byte(p))) + version[3] = REVISION_RC + pgm_read_byte(p) - '1'; + else + return false; + } + } else + return false; + } + +#if 0 + SERIAL_ECHOPGM("Version parsed, major: "); + SERIAL_ECHO(version[0]); + SERIAL_ECHOPGM(", minor: "); + SERIAL_ECHO(version[1]); + SERIAL_ECHOPGM(", revision: "); + SERIAL_ECHO(version[2]); + SERIAL_ECHOPGM(", flavor: "); + SERIAL_ECHO(version[3]); + SERIAL_ECHOLNPGM(""); +#endif + return true; +} + +// 1 - yes, 0 - false, -1 - error; +inline int8_t is_provided_version_newer(const char *version_string) +{ + uint16_t ver_gcode[3], ver_current[3]; + if (! parse_version(version_string, ver_gcode)) + return -1; + if (! parse_version_P(FW_VERSION_STR, ver_current)) + return 0; // this shall not happen + for (uint8_t i = 0; i < 3; ++ i) + if (ver_gcode[i] > ver_current[i]) + return 1; + return 0; +} + +bool show_upgrade_dialog_if_version_newer(const char *version_string) +{ + uint16_t ver_gcode[4], ver_current[4]; + if (! parse_version(version_string, ver_gcode)) { +// SERIAL_PROTOCOLLNPGM("parse_version failed"); + return false; + } + if (! parse_version_P(FW_VERSION_STR, ver_current)) { +// SERIAL_PROTOCOLLNPGM("parse_version_P failed"); + return false; // this shall not happen + } +// SERIAL_PROTOCOLLNPGM("versions parsed"); + bool upgrade = false; + for (uint8_t i = 0; i < 4; ++ i) { + if (ver_gcode[i] > ver_current[i]) { + upgrade = true; + break; + } else if (ver_gcode[i] < ver_current[i]) + break; + } + + if (upgrade) { + lcd_display_message_fullscreen_P(MSG_NEW_FIRMWARE_AVAILABLE); + lcd_print_at_PGM(0, 2, PSTR("")); + for (const char *c = version_string; ! is_whitespace_or_nl_or_eol(*c); ++ c) + lcd_implementation_write(*c); + lcd_print_at_PGM(0, 3, MSG_NEW_FIRMWARE_PLEASE_UPGRADE); + tone(BEEPER, 1000); + delay_keep_alive(50); + noTone(BEEPER); + delay_keep_alive(500); + tone(BEEPER, 1000); + delay_keep_alive(50); + noTone(BEEPER); + lcd_wait_for_click(); + lcd_update_enable(true); + lcd_implementation_clear(); + lcd_update(); + } + + // Succeeded. + return true; +} diff --git a/Firmware/util.h b/Firmware/util.h new file mode 100644 index 00000000..a07b45e3 --- /dev/null +++ b/Firmware/util.h @@ -0,0 +1,8 @@ +#ifndef UTIL_H +#define UTIL_H + +extern const char* FW_VERSION_STR_P(); + +extern bool show_upgrade_dialog_if_version_newer(const char *version_string); + +#endif /* UTIL_H */