diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 8ee38c0093b9..a53c62352f4e 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -112,6 +112,7 @@ * :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] */ #define BAUDRATE 250000 + //#define BAUD_RATE_GCODE // Enable G-code M575 to set the baud rate /** @@ -120,7 +121,7 @@ * :[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7] */ //#define SERIAL_PORT_2 -1 -//#define BAUDRATE_2 250000 // Enable to override BAUDRATE +//#define BAUDRATE_2 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE /** * Select a third serial port on the board to use for communication with the host. @@ -128,7 +129,7 @@ * :[-1, 0, 1, 2, 3, 4, 5, 6, 7] */ //#define SERIAL_PORT_3 1 -//#define BAUDRATE_3 250000 // Enable to override BAUDRATE +//#define BAUDRATE_3 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE // Enable the Bluetooth serial interface on AT90USB devices //#define BLUETOOTH @@ -386,7 +387,7 @@ //#define HOTEND_OFFSET_Y { 0.0, 5.00 } // (mm) relative Y-offset for each nozzle //#define HOTEND_OFFSET_Z { 0.0, 0.00 } // (mm) relative Z-offset for each nozzle -// @section machine +// @section psu control /** * Power Supply Control @@ -632,6 +633,8 @@ //============================= PID Settings ================================ //=========================================================================== +// @section hotend temp + // Enable PIDTEMP for PID control or MPCTEMP for Predictive Model. // temperature control. Disable both for bang-bang heating. #define PIDTEMP // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning @@ -665,6 +668,7 @@ * Use a physical model of the hotend to control temperature. When configured correctly * this gives better responsiveness and stability than PID and it also removes the need * for PID_EXTRUSION_SCALING and PID_FAN_SCALING. Use M306 T to autotune the model. + * @section mpctemp */ #if ENABLED(MPCTEMP) //#define MPC_EDIT_MENU // Add MPC editing to the "Advanced Settings" menu. (~1300 bytes of flash) @@ -717,6 +721,7 @@ * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 250W * heater. If your configuration is significantly different than this and you don't understand * the issues involved, don't use bed PID until someone else verifies that your hardware works. + * @section bed temp */ //#define PIDTEMPBED @@ -760,6 +765,7 @@ * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 200W * heater. If your configuration is significantly different than this and you don't understand * the issues involved, don't use chamber PID until someone else verifies that your hardware works. + * @section chamber temp */ //#define PIDTEMPCHAMBER //#define CHAMBER_LIMIT_SWITCHING @@ -797,7 +803,7 @@ //#define PID_AUTOTUNE_MENU // Add PID auto-tuning to the "Advanced Settings" menu. (~250 bytes of flash) #endif -// @section extruder +// @section safety /** * Prevent extrusion if the temperature is below EXTRUDE_MINTEMP. @@ -865,6 +871,8 @@ #define POLAR_SEGMENTS_PER_SECOND 5 #endif +// @section delta + // Enable for DELTA kinematics and configure below //#define DELTA #if ENABLED(DELTA) @@ -924,6 +932,8 @@ //#define DELTA_DIAGONAL_ROD_TRIM_TOWER { 0.0, 0.0, 0.0 } #endif +// @section scara + /** * MORGAN_SCARA was developed by QHARLEY in South Africa in 2012-2013. * Implemented and slightly reworked by JCERNY in June, 2014. @@ -967,6 +977,8 @@ #endif +// @section tpara + // Enable for TPARA kinematics and configure below //#define AXEL_TPARA #if ENABLED(AXEL_TPARA) @@ -993,6 +1005,8 @@ #define PSI_HOMING_OFFSET 0 #endif +// @section machine + // Articulated robot (arm). Joints are directly mapped to axes with no kinematics. //#define ARTICULATED_ROBOT_ARM @@ -1004,7 +1018,7 @@ //============================== Endstop Settings =========================== //=========================================================================== -// @section homing +// @section endstops // Specify here all the endstop connectors that are connected to any endstop or probe. // Almost all printers will be using one per axis. Probes will use one or more of the @@ -1668,7 +1682,7 @@ //#define V_HOME_DIR -1 //#define W_HOME_DIR -1 -// @section machine +// @section geometry // The size of the printable area #define X_BED_SIZE 200 @@ -2128,7 +2142,7 @@ //============================= Additional Features =========================== //============================================================================= -// @section extras +// @section eeprom /** * EEPROM @@ -2148,6 +2162,8 @@ //#define EEPROM_INIT_NOW // Init EEPROM on first boot after a new build. #endif +// @section host + // // Host Keepalive // @@ -2158,6 +2174,8 @@ #define DEFAULT_KEEPALIVE_INTERVAL 2 // Number of seconds between "busy" messages. Set with M113. #define BUSY_WHILE_HEATING // Some hosts require "busy" messages even during heating +// @section units + // // G20/G21 Inch mode support // @@ -2185,6 +2203,8 @@ #define PREHEAT_2_TEMP_CHAMBER 35 #define PREHEAT_2_FAN_SPEED 0 // Value from 0 to 255 +// @section motion + /** * Nozzle Park * @@ -2283,6 +2303,8 @@ #endif +// @section host + /** * Print Job Timer * @@ -2309,6 +2331,8 @@ */ #define PRINTJOB_TIMER_AUTOSTART +// @section stats + /** * Print Counter * @@ -2326,6 +2350,8 @@ #define PRINTCOUNTER_SAVE_INTERVAL 60 // (minutes) EEPROM save interval during print #endif +// @section security + /** * Password * @@ -2361,7 +2387,7 @@ //============================= LCD and SD support ============================ //============================================================================= -// @section lcd +// @section interface /** * LCD LANGUAGE @@ -2517,6 +2543,7 @@ //======================== LCD / Controller Selection ========================= //======================== (Character-based LCDs) ========================= //============================================================================= +// @section lcd // // RepRapDiscount Smart Controller. @@ -3151,7 +3178,7 @@ //=============================== Extra Features ============================== //============================================================================= -// @section extras +// @section fans // Set number of user-controlled fans. Disable to use all board-defined fans. // :[1,2,3,4,5,6,7,8] @@ -3175,14 +3202,18 @@ // duty cycle is attained. //#define SOFT_PWM_DITHER +// @section extras + +// Support for the BariCUDA Paste Extruder +//#define BARICUDA + +// @section lights + // Temperature status LEDs that display the hotend and bed temperature. // If all hotends, bed temperature, and target temperature are under 54C // then the BLUE led is on. Otherwise the RED led is on. (1C hysteresis) //#define TEMP_STAT_LEDS -// Support for the BariCUDA Paste Extruder -//#define BARICUDA - // Support for BlinkM/CyzRgb //#define BLINKM @@ -3268,6 +3299,8 @@ #define PRINTER_EVENT_LEDS #endif +// @section servos + /** * Number of servos * diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 89c0a04e9287..6a82b0c000f0 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -32,6 +32,8 @@ */ #define CONFIGURATION_ADV_H_VERSION 02010100 +// @section develop + /** * Configuration Dump * @@ -2561,6 +2563,8 @@ #endif #endif // HAS_MULTI_EXTRUDER +// @section advanced pause + /** * Advanced Pause for Filament Change * - Adds the G-code M600 Filament Change to initiate a filament change. @@ -2619,13 +2623,12 @@ //#define FILAMENT_UNLOAD_ALL_EXTRUDERS // Allow M702 to unload all extruders above a minimum target temp (as set by M302) #endif -// @section tmc - /** * TMC26X Stepper Driver options * * The TMC26XStepper library is required for this stepper driver. * https://github.com/trinamic/TMC26XStepper + * @section tmc/tmc26x */ #if HAS_DRIVER(TMC26X) @@ -2763,8 +2766,6 @@ #endif // TMC26X -// @section tmc_smart - /** * To use TMC2130, TMC2160, TMC2660, TMC5130, TMC5160 stepper drivers in SPI mode * connect your SPI pins to the hardware SPI interface on your board and define @@ -2780,6 +2781,7 @@ * * TMCStepper library is required to use TMC stepper drivers. * https://github.com/teemuatlut/TMCStepper + * @section tmc/config */ #if HAS_TRINAMIC_CONFIG @@ -3003,6 +3005,8 @@ //#define E7_HOLD_MULTIPLIER 0.5 #endif + // @section tmc/spi + /** * Override default SPI pins for TMC2130, TMC2160, TMC2660, TMC5130 and TMC5160 drivers here. * The default pins can be found in your board's pins file. @@ -3040,6 +3044,8 @@ //#define TMC_SW_MISO -1 //#define TMC_SW_SCK -1 + // @section tmc/serial + /** * Four TMC2209 drivers can use the same HW/SW serial port with hardware configured addresses. * Set the address using jumpers on pins MS1 and MS2. @@ -3075,6 +3081,8 @@ //#define E6_SLAVE_ADDRESS 0 //#define E7_SLAVE_ADDRESS 0 + // @section tmc/smart + /** * Software enable * @@ -3083,6 +3091,8 @@ */ //#define SOFTWARE_DRIVER_ENABLE + // @section tmc/stealthchop + /** * TMC2130, TMC2160, TMC2208, TMC2209, TMC5130 and TMC5160 only * Use Trinamic's ultra quiet stepping mode. @@ -3137,6 +3147,8 @@ //#define CHOPPER_TIMING_E6 CHOPPER_TIMING_E //#define CHOPPER_TIMING_E7 CHOPPER_TIMING_E + // @section tmc/status + /** * Monitor Trinamic drivers * for error conditions like overtemperature and short to ground. @@ -3156,6 +3168,8 @@ #define STOP_ON_ERROR #endif + // @section tmc/hybrid + /** * TMC2130, TMC2160, TMC2208, TMC2209, TMC5130 and TMC5160 only * The driver will switch to spreadCycle when stepper speed is over HYBRID_THRESHOLD. @@ -3212,6 +3226,7 @@ * homing and adds a guard period for endstop triggering. * * Comment *_STALL_SENSITIVITY to disable sensorless homing for that axis. + * @section tmc/stallguard */ //#define SENSORLESS_HOMING // StallGuard capable drivers only @@ -3235,6 +3250,8 @@ //#define IMPROVE_HOMING_RELIABILITY #endif + // @section tmc/config + /** * TMC Homing stepper phase. * @@ -3274,7 +3291,6 @@ #endif // HAS_TRINAMIC_CONFIG - // @section i2cbus // @@ -3316,7 +3332,7 @@ #define I2C_SLAVE_ADDRESS 0 // Set a value from 8 to 127 to act as a slave #endif -// @section extras +// @section photo /** * Photo G-code @@ -3359,6 +3375,8 @@ #endif #endif +// @section cnc + /** * Spindle & Laser control * @@ -3562,6 +3580,8 @@ #define COOLANT_FLOOD_INVERT false // Set "true" if the on/off function is reversed #endif +// @section filament width + /** * Filament Width Sensor * @@ -3595,6 +3615,8 @@ //#define FILAMENT_LCD_DISPLAY #endif +// @section power + /** * Power Monitor * Monitor voltage (V) and/or current (A), and -when possible- power (W) @@ -3618,6 +3640,8 @@ #define POWER_MONITOR_VOLTAGE_OFFSET 0 // Offset (in volts) applied to the calculated voltage #endif +// @section safety + /** * Stepper Driver Anti-SNAFU Protection * @@ -3627,6 +3651,8 @@ */ //#define DISABLE_DRIVER_SAFE_POWER_PROTECT +// @section cnc + /** * CNC Coordinate Systems * @@ -3635,6 +3661,8 @@ */ //#define CNC_COORDINATE_SYSTEMS +// @section reporting + /** * Auto-report fan speed with M123 S * Requires fans with tachometer pins @@ -3662,6 +3690,8 @@ //#define M115_GEOMETRY_REPORT #endif +// @section security + /** * Expected Printer Check * Add the M16 G-code to compare a string to the MACHINE_NAME. @@ -3669,6 +3699,8 @@ */ //#define EXPECTED_PRINTER_CHECK +// @section volumetrics + /** * Disable all Volumetric extrusion options */ @@ -3697,14 +3729,7 @@ #endif #endif -/** - * Enable this option for a leaner build of Marlin that removes all - * workspace offsets, simplifying coordinate transformations, leveling, etc. - * - * - M206 and M428 are disabled. - * - G92 will revert to its behavior from Marlin 1.0. - */ -//#define NO_WORKSPACE_OFFSETS +// @section reporting // Extra options for the M114 "Current Position" report //#define M114_DETAIL // Use 'M114` for details to check planner calculations @@ -3713,6 +3738,8 @@ //#define REPORT_FAN_CHANGE // Report the new fan speed when changed by M106 (and others) +// @section gcode + /** * Spend 28 bytes of SRAM to optimize the G-code parser */ @@ -3730,6 +3757,15 @@ //#define REPETIER_GCODE_M360 // Add commands originally from Repetier FW +/** + * Enable this option for a leaner build of Marlin that removes all + * workspace offsets, simplifying coordinate transformations, leveling, etc. + * + * - M206 and M428 are disabled. + * - G92 will revert to its behavior from Marlin 1.0. + */ +//#define NO_WORKSPACE_OFFSETS + /** * CNC G-code options * Support CNC-style G-code dialects used by laser cutters, drawing machine cams, etc. @@ -3745,6 +3781,8 @@ //#define VARIABLE_G0_FEEDRATE // The G0 feedrate is set by F in G0 motion mode #endif +// @section gcode + /** * Startup commands * @@ -3769,6 +3807,8 @@ * Up to 25 may be defined, but the actual number is LCD-dependent. */ +// @section custom main menu + // Custom Menu: Main Menu //#define CUSTOM_MENU_MAIN #if ENABLED(CUSTOM_MENU_MAIN) @@ -3799,6 +3839,8 @@ //#define MAIN_MENU_ITEM_5_CONFIRM #endif +// @section custom config menu + // Custom Menu: Configuration Menu //#define CUSTOM_MENU_CONFIG #if ENABLED(CUSTOM_MENU_CONFIG) @@ -3829,6 +3871,8 @@ //#define CONFIG_MENU_ITEM_5_CONFIRM #endif +// @section custom buttons + /** * User-defined buttons to run custom G-code. * Up to 25 may be defined. @@ -3860,6 +3904,8 @@ #endif #endif +// @section host + /** * Host Action Commands * @@ -3885,6 +3931,8 @@ //#define HOST_SHUTDOWN_MENU_ITEM // Add a menu item that tells the host to shut down #endif +// @section extras + /** * Cancel Objects * @@ -3906,6 +3954,7 @@ * Alternative Supplier: https://reliabuild3d.com/ * * Reliabuild encoders have been modified to improve reliability. + * @section i2c encoders */ //#define I2C_POSITION_ENCODERS @@ -3977,6 +4026,7 @@ /** * Analog Joystick(s) + * @section joystick */ //#define JOYSTICK #if ENABLED(JOYSTICK) @@ -4001,6 +4051,7 @@ * Modern replacement for the Prusa TMC_Z_CALIBRATION. * Adds capability to work with any adjustable current drivers. * Implemented as G34 because M915 is deprecated. + * @section calibrate */ //#define MECHANICAL_GANTRY_CALIBRATION #if ENABLED(MECHANICAL_GANTRY_CALIBRATION) @@ -4018,6 +4069,7 @@ /** * Instant freeze / unfreeze functionality * Potentially useful for emergency stop that allows being resumed. + * @section interface */ //#define FREEZE_FEATURE #if ENABLED(FREEZE_FEATURE) @@ -4030,6 +4082,7 @@ * * Add support for a low-cost 8x8 LED Matrix based on the Max7219 chip as a realtime status display. * Requires 3 signal wires. Some useful debug options are included to demonstrate its usage. + * @section debug matrix */ //#define MAX7219_DEBUG #if ENABLED(MAX7219_DEBUG) @@ -4068,6 +4121,7 @@ * Support for Synchronized Z moves when used with NanoDLP. G0/G1 axis moves will * output a "Z_move_comp" string to enable synchronization with DLP projector exposure. * This feature allows you to use [[WaitForDoneMessage]] instead of M400 commands. + * @section nanodlp */ //#define NANODLP_Z_SYNC #if ENABLED(NANODLP_Z_SYNC) @@ -4076,6 +4130,7 @@ /** * Ethernet. Use M552 to enable and set the IP address. + * @section network */ #if HAS_ETHERNET #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xF0, 0x0D } // A MAC address unique to your network @@ -4103,6 +4158,8 @@ //#include "Configuration_Secure.h" // External file with WiFi SSID / Password #endif +// @section multi-material + /** * Průša Multi-Material Unit (MMU) * Enable in Configuration.h @@ -4208,6 +4265,7 @@ /** * Advanced Print Counter settings + * @section stats */ #if ENABLED(PRINTCOUNTER) #define SERVICE_WARNING_BUZZES 3 diff --git a/buildroot/share/PlatformIO/scripts/schema.py b/buildroot/share/PlatformIO/scripts/schema.py new file mode 100755 index 000000000000..efe56ebe3583 --- /dev/null +++ b/buildroot/share/PlatformIO/scripts/schema.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python3 +# +# schema.py +# +# Used by signature.py via common-dependencies.py to generate a schema file during the PlatformIO build. +# This script can also be run standalone from within the Marlin repo to generate all schema files. +# +import re,json +from pathlib import Path + +def extend_dict(d:dict, k:tuple): + if len(k) >= 1 and k[0] not in d: + d[k[0]] = {} + if len(k) >= 2 and k[1] not in d[k[0]]: + d[k[0]][k[1]] = {} + if len(k) >= 3 and k[2] not in d[k[0]][k[1]]: + d[k[0]][k[1]][k[2]] = {} + +grouping_patterns = [ + re.compile(r'^([XYZIJKUVW]|[XYZ]2|Z[34]|E[0-7])$'), + re.compile(r'^AXIS\d$'), + re.compile(r'^(MIN|MAX)$'), + re.compile(r'^[0-8]$'), + re.compile(r'^HOTEND[0-7]$'), + re.compile(r'^(HOTENDS|BED|PROBE|COOLER)$'), + re.compile(r'^[XYZIJKUVW]M(IN|AX)$') +] +# If the indexed part of the option name matches a pattern +# then add it to the dictionary. +def find_grouping(gdict, filekey, sectkey, optkey, pindex): + optparts = optkey.split('_') + if 1 < len(optparts) > pindex: + for patt in grouping_patterns: + if patt.match(optparts[pindex]): + subkey = optparts[pindex] + modkey = '_'.join(optparts) + optparts[pindex] = '*' + wildkey = '_'.join(optparts) + kkey = f'{filekey}|{sectkey}|{wildkey}' + if kkey not in gdict: gdict[kkey] = [] + gdict[kkey].append((subkey, modkey)) + +# Build a list of potential groups. Only those with multiple items will be grouped. +def group_options(schema): + for pindex in range(10, -1, -1): + found_groups = {} + for filekey, f in schema.items(): + for sectkey, s in f.items(): + for optkey in s: + find_grouping(found_groups, filekey, sectkey, optkey, pindex) + + fkeys = [ k for k in found_groups.keys() ] + for kkey in fkeys: + items = found_groups[kkey] + if len(items) > 1: + f, s, w = kkey.split('|') + extend_dict(schema, (f, s, w)) # Add wildcard group to schema + for subkey, optkey in items: # Add all items to wildcard group + schema[f][s][w][subkey] = schema[f][s][optkey] # Move non-wildcard item to wildcard group + del schema[f][s][optkey] + del found_groups[kkey] + +# Extract all board names from boards.h +def load_boards(): + bpath = Path("Marlin/src/core/boards.h") + if bpath.is_file(): + with bpath.open() as bfile: + boards = [] + for line in bfile: + if line.startswith("#define BOARD_"): + bname = line.split()[1] + if bname != "BOARD_UNKNOWN": boards.append(bname) + return "['" + "','".join(boards) + "']" + return '' + +# +# Extract a schema from the current configuration files +# +def extract(): + # Load board names from boards.h + boards = load_boards() + + # Parsing states + class Parse: + NORMAL = 0 # No condition yet + BLOCK_COMMENT = 1 # Looking for the end of the block comment + EOL_COMMENT = 2 # EOL comment started, maybe add the next comment? + GET_SENSORS = 3 # Gathering temperature sensor options + ERROR = 9 # Syntax error + + # List of files to process, with shorthand + filekey = { 'Configuration.h':'basic', 'Configuration_adv.h':'advanced' } + # A JSON object to store the data + sch_out = { 'basic':{}, 'advanced':{} } + # Regex for #define NAME [VALUE] [COMMENT] with sanitized line + defgrep = re.compile(r'^(//)?\s*(#define)\s+([A-Za-z0-9_]+)\s*(.*?)\s*(//.+)?$') + # Defines to ignore + ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_DUMP') + # Start with unknown state + state = Parse.NORMAL + # Serial ID + sid = 0 + # Loop through files and parse them line by line + for fn, fk in filekey.items(): + with Path("Marlin", fn).open() as fileobj: + section = 'none' # Current Settings section + line_number = 0 # Counter for the line number of the file + conditions = [] # Create a condition stack for the current file + comment_buff = [] # A temporary buffer for comments + options_json = '' # A buffer for the most recent options JSON found + eol_options = False # The options came from end of line, so only apply once + join_line = False # A flag that the line should be joined with the previous one + line = '' # A line buffer to handle \ continuation + last_added_ref = None # Reference to the last added item + # Loop through the lines in the file + for the_line in fileobj.readlines(): + line_number += 1 + + # Clean the line for easier parsing + the_line = the_line.strip() + + if join_line: # A previous line is being made longer + line += (' ' if line else '') + the_line + else: # Otherwise, start the line anew + line, line_start = the_line, line_number + + # If the resulting line ends with a \, don't process now. + # Strip the end off. The next line will be joined with it. + join_line = line.endswith("\\") + if join_line: + line = line[:-1].strip() + continue + else: + line_end = line_number + + defmatch = defgrep.match(line) + + # Special handling for EOL comments after a #define. + # At this point the #define is already digested and inserted, + # so we have to extend it + if state == Parse.EOL_COMMENT: + # If the line is not a comment, we're done with the EOL comment + if not defmatch and the_line.startswith('//'): + comment_buff.append(the_line[2:].strip()) + else: + last_added_ref['comment'] = ' '.join(comment_buff) + comment_buff = [] + state = Parse.NORMAL + + def use_comment(c, opt, sec, bufref): + if c.startswith(':'): # If the comment starts with : then it has magic JSON + d = c[1:].strip() # Strip the leading : + cbr = c.rindex('}') if d.startswith('{') else c.rindex(']') if d.startswith('[') else 0 + if cbr: + opt, cmt = c[1:cbr+1].strip(), c[cbr+1:].strip() + if cmt != '': bufref.append(cmt) + else: + opt = c[1:].strip() + elif c.startswith('@section'): # Start a new section + sec = c[8:].strip() + elif not c.startswith('========'): + bufref.append(c) + return opt, sec + + # In a block comment, capture lines up to the end of the comment. + # Assume nothing follows the comment closure. + if state in (Parse.BLOCK_COMMENT, Parse.GET_SENSORS): + endpos = line.find('*/') + if endpos < 0: + cline = line + else: + cline, line = line[:endpos].strip(), line[endpos+2:].strip() + + # Temperature sensors are done + if state == Parse.GET_SENSORS: + options_json = f'[ {options_json[:-2]} ]' + + state = Parse.NORMAL + + # Strip the leading '*' from block comments + if cline.startswith('*'): cline = cline[1:].strip() + + # Collect temperature sensors + if state == Parse.GET_SENSORS: + sens = re.match(r'^(-?\d+)\s*:\s*(.+)$', cline) + if sens: + s2 = sens[2].replace("'","''") + options_json += f"{sens[1]}:'{s2}', " + + elif state == Parse.BLOCK_COMMENT: + + # Look for temperature sensors + if cline == "Temperature sensors available:": + state, cline = Parse.GET_SENSORS, "Temperature Sensors" + + options_json, section = use_comment(cline, options_json, section, comment_buff) + + # For the normal state we're looking for any non-blank line + elif state == Parse.NORMAL: + # Skip a commented define when evaluating comment opening + st = 2 if re.match(r'^//\s*#define', line) else 0 + cpos1 = line.find('/*') # Start a block comment on the line? + cpos2 = line.find('//', st) # Start an end of line comment on the line? + + # Only the first comment starter gets evaluated + cpos = -1 + if cpos1 != -1 and (cpos1 < cpos2 or cpos2 == -1): + cpos = cpos1 + comment_buff = [] + state = Parse.BLOCK_COMMENT + eol_options = False + + elif cpos2 != -1 and (cpos2 < cpos1 or cpos1 == -1): + cpos = cpos2 + + # Expire end-of-line options after first use + if cline.startswith(':'): eol_options = True + + # Comment after a define may be continued on the following lines + if state == Parse.NORMAL and defmatch != None and cpos > 10: + state = Parse.EOL_COMMENT + comment_buff = [] + + # Process the start of a new comment + if cpos != -1: + cline, line = line[cpos+2:].strip(), line[:cpos].strip() + + # Strip leading '*' from block comments + if state == Parse.BLOCK_COMMENT: + if cline.startswith('*'): cline = cline[1:].strip() + + # Buffer a non-empty comment start + if cline != '': + options_json, section = use_comment(cline, options_json, section, comment_buff) + + # If the line has nothing before the comment, go to the next line + if line == '': + options_json = '' + continue + + # Parenthesize the given expression if needed + def atomize(s): + if s == '' \ + or re.match(r'^[A-Za-z0-9_]*(\([^)]+\))?$', s) \ + or re.match(r'^[A-Za-z0-9_]+ == \d+?$', s): + return s + return f'({s})' + + # + # The conditions stack is an array containing condition-arrays. + # Each condition-array lists the conditions for the current block. + # IF/N/DEF adds a new condition-array to the stack. + # ELSE/ELIF/ENDIF pop the condition-array. + # ELSE/ELIF negate the last item in the popped condition-array. + # ELIF adds a new condition to the end of the array. + # ELSE/ELIF re-push the condition-array. + # + cparts = line.split() + iselif, iselse = cparts[0] == '#elif', cparts[0] == '#else' + if iselif or iselse or cparts[0] == '#endif': + if len(conditions) == 0: + raise Exception(f'no #if block at line {line_number}') + + # Pop the last condition-array from the stack + prev = conditions.pop() + + if iselif or iselse: + prev[-1] = '!' + prev[-1] # Invert the last condition + if iselif: prev.append(atomize(line[5:].strip())) + conditions.append(prev) + + elif cparts[0] == '#if': + conditions.append([ atomize(line[3:].strip()) ]) + elif cparts[0] == '#ifdef': + conditions.append([ f'defined({line[6:].strip()})' ]) + elif cparts[0] == '#ifndef': + conditions.append([ f'!defined({line[7:].strip()})' ]) + + # Handle a complete #define line + elif defmatch != None: + + # Get the match groups into vars + enabled, define_name, val = defmatch[1] == None, defmatch[3], defmatch[4] + + # Increment the serial ID + sid += 1 + + # Create a new dictionary for the current #define + define_info = { + 'section': section, + 'name': define_name, + 'enabled': enabled, + 'line': line_start, + 'sid': sid + } + + if val != '': define_info['value'] = val + + # Type is based on the value + if val == '': + value_type = 'switch' + elif re.match(r'^(true|false)$', val): + value_type = 'bool' + val = val == 'true' + elif re.match(r'^[-+]?\s*\d+$', val): + value_type = 'int' + val = int(val) + elif re.match(r'[-+]?\s*(\d+\.|\d*\.\d+)([eE][-+]?\d+)?[fF]?', val): + value_type = 'float' + val = float(val.replace('f','')) + else: + value_type = 'string' if val[0] == '"' \ + else 'char' if val[0] == "'" \ + else 'state' if re.match(r'^(LOW|HIGH)$', val) \ + else 'enum' if re.match(r'^[A-Za-z0-9_]{3,}$', val) \ + else 'int[]' if re.match(r'^{(\s*[-+]?\s*\d+\s*(,\s*)?)+}$', val) \ + else 'float[]' if re.match(r'^{(\s*[-+]?\s*(\d+\.|\d*\.\d+)([eE][-+]?\d+)?[fF]?\s*(,\s*)?)+}$', val) \ + else 'array' if val[0] == '{' \ + else '' + + if value_type != '': define_info['type'] = value_type + + # Join up accumulated conditions with && + if conditions: define_info['requires'] = ' && '.join(sum(conditions, [])) + + # If the comment_buff is not empty, add the comment to the info + if comment_buff: + full_comment = '\n'.join(comment_buff) + + # An EOL comment will be added later + # The handling could go here instead of above + if state == Parse.EOL_COMMENT: + define_info['comment'] = '' + else: + define_info['comment'] = full_comment + comment_buff = [] + + # If the comment specifies units, add that to the info + units = re.match(r'^\(([^)]+)\)', full_comment) + if units: + units = units[1] + if units == 's' or units == 'sec': units = 'seconds' + define_info['units'] = units + + # Set the options for the current #define + if define_name == "MOTHERBOARD" and boards != '': + define_info['options'] = boards + elif options_json != '': + define_info['options'] = options_json + if eol_options: options_json = '' + + # Create section dict if it doesn't exist yet + if section not in sch_out[fk]: sch_out[fk][section] = {} + + # If define has already been seen... + if define_name in sch_out[fk][section]: + info = sch_out[fk][section][define_name] + if isinstance(info, dict): info = [ info ] # Convert a single dict into a list + info.append(define_info) # Add to the list + else: + # Add the define dict with name as key + sch_out[fk][section][define_name] = define_info + + if state == Parse.EOL_COMMENT: + last_added_ref = define_info + + return sch_out + +def dump_json(schema:dict, jpath:Path): + with jpath.open('w') as jfile: + json.dump(schema, jfile, ensure_ascii=False, indent=2) + +def dump_yaml(schema:dict, ypath:Path): + import yaml + with ypath.open('w') as yfile: + yaml.dump(schema, yfile, default_flow_style=False, width=120, indent=2) + +def main(): + try: + schema = extract() + except Exception as exc: + print("Error: " + str(exc)) + schema = None + + if schema: + print("Generating JSON ...") + dump_json(schema, Path('schema.json')) + group_options(schema) + dump_json(schema, Path('schema_grouped.json')) + + try: + import yaml + except ImportError: + print("Installing YAML module ...") + import subprocess + subprocess.run(['python3', '-m', 'pip', 'install', 'pyyaml']) + import yaml + + print("Generating YML ...") + dump_yaml(schema, Path('schema.yml')) + +if __name__ == '__main__': + main() diff --git a/buildroot/share/PlatformIO/scripts/signature.py b/buildroot/share/PlatformIO/scripts/signature.py index 7b684c4f8267..1ed5c10dae20 100644 --- a/buildroot/share/PlatformIO/scripts/signature.py +++ b/buildroot/share/PlatformIO/scripts/signature.py @@ -1,7 +1,9 @@ # # signature.py # -import os,subprocess,re,json,hashlib +import subprocess,re,json,hashlib +import schema +from pathlib import Path # # Return all macro names in a header as an array, so we can take @@ -51,15 +53,15 @@ def compute_build_signature(env): # Definitions from these files will be kept files_to_keep = [ 'Marlin/Configuration.h', 'Marlin/Configuration_adv.h' ] - build_dir = os.path.join(env['PROJECT_BUILD_DIR'], env['PIOENV']) + build_path = Path(env['PROJECT_BUILD_DIR'], env['PIOENV']) # Check if we can skip processing hashes = '' for header in files_to_keep: hashes += get_file_sha256sum(header)[0:10] - marlin_json = os.path.join(build_dir, 'marlin_config.json') - marlin_zip = os.path.join(build_dir, 'mc') + marlin_json = build_path / 'marlin_config.json' + marlin_zip = build_path / 'mc' # Read existing config file try: @@ -164,8 +166,8 @@ def tryint(key): print("Generating config.ini ...") ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_DUMP') filegrp = { 'Configuration.h':'config:basic', 'Configuration_adv.h':'config:advanced' } - config_ini = os.path.join(build_dir, 'config.ini') - with open(config_ini, 'w') as outfile: + config_ini = build_path / 'config.ini' + with config_ini.open('w') as outfile: outfile.write('#\n# Marlin Firmware\n# config.ini - Options to apply before the build\n#\n') # Loop through the data array of arrays for header in data: @@ -177,6 +179,42 @@ def tryint(key): val = 'on' if data[header][key] == '' else data[header][key] outfile.write('{0:40}{1}'.format(key.lower(), ' = ' + val) + '\n') + # + # Produce a schema.json file if CONFIG_DUMP == 3 + # + if config_dump >= 3: + try: + conf_schema = schema.extract() + except Exception as exc: + print("Error: " + str(exc)) + conf_schema = None + + if conf_schema: + # + # Produce a schema.json file if CONFIG_DUMP == 3 + # + if config_dump in (3, 13): + print("Generating schema.json ...") + schema.dump_json(conf_schema, build_path / 'schema.json') + if config_dump == 13: + schema.group_options(conf_schema) + schema.dump_json(conf_schema, build_path / 'schema_grouped.json') + + # + # Produce a schema.yml file if CONFIG_DUMP == 4 + # + elif config_dump == 4: + print("Generating schema.yml ...") + try: + import yaml + except ImportError: + env.Execute(env.VerboseAction( + '$PYTHONEXE -m pip install "pyyaml"', + "Installing YAML for schema.yml export", + )) + import yaml + schema.dump_yaml(conf_schema, build_path / 'schema.yml') + # Append the source code version and date data['VERSION'] = {} data['VERSION']['DETAILED_BUILD_VERSION'] = resolved_defines['DETAILED_BUILD_VERSION'] @@ -188,10 +226,11 @@ def tryint(key): pass # - # Produce a JSON file for CONFIGURATION_EMBEDDING or CONFIG_DUMP > 0 + # Produce a JSON file for CONFIGURATION_EMBEDDING or CONFIG_DUMP == 1 # - with open(marlin_json, 'w') as outfile: - json.dump(data, outfile, separators=(',', ':')) + if config_dump == 1 or 'CONFIGURATION_EMBEDDING' in defines: + with open(marlin_json, 'w') as outfile: + json.dump(data, outfile, separators=(',', ':')) # # The rest only applies to CONFIGURATION_EMBEDDING @@ -211,11 +250,11 @@ def tryint(key): + b'const unsigned char mc_zip[] PROGMEM = {\n ' ) count = 0 - for b in open(os.path.join(build_dir, 'mc.zip'), 'rb').read(): + for b in (build_path / 'mc.zip').open('rb').read(): result_file.write(b' 0x%02X,' % b) count += 1 - if (count % 16 == 0): - result_file.write(b'\n ') - if (count % 16): + if count % 16 == 0: + result_file.write(b'\n ') + if count % 16: result_file.write(b'\n') result_file.write(b'};\n')