diff --git a/configs/sim/axis/gladevcp/meter_scale.py b/configs/sim/axis/gladevcp/meter_scale.py index 984af6c0148..dddd8d95708 100644 --- a/configs/sim/axis/gladevcp/meter_scale.py +++ b/configs/sim/axis/gladevcp/meter_scale.py @@ -18,7 +18,7 @@ def __init__(self, halcomp,builder,useropts): self.max_value.connect('value-changed', self._on_max_value_change) inifile = linuxcnc.ini(os.getenv("INI_FILE_NAME")) - mmax = float(inifile.find("METER", "MAX") or 100.0) + mmax = inifile.getreal("METER", "MAX", fallback=100.0) self.meter = self.builder.get_object('meter') self.max_value.set(mmax) diff --git a/configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py b/configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py index 70ee0f8290b..f3ded2f02ff 100755 --- a/configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py +++ b/configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py @@ -39,20 +39,17 @@ # set up parsing of the inifile import os -import configparser +import linuxcnc # get the path for the ini file used to start this config inifile = os.environ.get("INI_FILE_NAME") -# instantiate a parser in non-strict mode because we have multiple entries for -# some sections in the ini -config = configparser.ConfigParser(strict=False) -# ingest the ini file -config.read(inifile) +# instantiate the LinuxCNC ini-parser +config = linuxcnc.ini(inifile) ## SPINDLE ROTARY JOINT LETTERS # spindle primary joint -joint_letter_primary = (config['TWP']['PRIMARY']).capitalize() +joint_letter_primary = config.getstring('TWP', 'PRIMARY', fallback="").capitalize() # spindle secondary joint (ie the one closer to the tool) -joint_letter_secondary = (config['TWP']['SECONDARY']).capitalize() +joint_letter_secondary = config.getstring('TWP', 'SECONDARY', fallback="").capitalize() if not joint_letter_primary in ('A','B','C') or not joint_letter_secondary in ('A','B','C'): log.error("Unable to parse joint letters given in INI [TWP].") @@ -61,18 +58,18 @@ else: # get the MIN/MAX limits of the respective rotary joint letters category = 'AXIS_' + joint_letter_primary - primary_min_limit = float(config[category]['MIN_LIMIT']) - primary_max_limit = float(config[category]['MAX_LIMIT']) + primary_min_limit = config.getreal(category, 'MIN_LIMIT', fallback=0.0) + primary_max_limit = config.getreal(category, 'MAX_LIMIT', fallback=0.0) log.info('Joint letter for primary is %s with MIN/MAX limits: %s,%s', joint_letter_primary, primary_min_limit, primary_max_limit) category = 'AXIS_' + joint_letter_secondary - secondary_min_limit = float(config[category]['MIN_LIMIT']) - secondary_max_limit = float(config[category]['MAX_LIMIT']) + secondary_min_limit = config.getreal(category, 'MIN_LIMIT', fallback=0.0) + secondary_max_limit = config.gerreal(category, 'MAX_LIMIT', fallback=0.0) log.info('Joint letter for secondary is %s with MIN/MAX Limits: %s,%s', joint_letter_secondary, secondary_min_limit, secondary_max_limit) ## CONNECTIONS TO THE KINEMATIC COMPONENT -# get the name of the kinematic component (this seems to ingest also the next line) -kins_comp = (config['KINS']['KINEMATICS']).partition('\n')[0] +# get the name of the kinematic component +kins_comp = config.getstring('KINS', 'KINEMATICS', fallback="") # name of the hal pin that represents the nutation-angle kins_nutation_angle = kins_comp + '_kins.nut-angle' # name of the hal pin that represents the pre-rotation diff --git a/debian/linuxcnc.install.in b/debian/linuxcnc.install.in index 62cb17f1849..4bec8d5537d 100644 --- a/debian/linuxcnc.install.in +++ b/debian/linuxcnc.install.in @@ -34,6 +34,7 @@ usr/bin/hexagui usr/bin/hy_gt_vfd usr/bin/hy_vfd usr/bin/image-to-gcode +usr/bin/inivalue usr/bin/inivar usr/bin/latency-histogram usr/bin/latency-plot diff --git a/debian/linuxcnc.lintian-overrides.in b/debian/linuxcnc.lintian-overrides.in index 8ae6da9c35c..ad0eff91335 100644 --- a/debian/linuxcnc.lintian-overrides.in +++ b/debian/linuxcnc.lintian-overrides.in @@ -9,7 +9,7 @@ linuxcnc-uspace: elevated-privileges 4755 root/root [usr/bin/linuxcnc_module_hel linuxcnc-uspace: elevated-privileges 4755 root/root [usr/bin/rtapi_app] # that is intentional - for now -linuxcnc-uspace: package-name-doesnt-match-sonames liblinuxcnchal0 liblinuxcncini0 libnml0 libposemath0 libpyplugin0 librs274-0 libtooldata0 +linuxcnc-uspace: package-name-doesnt-match-sonames liblinuxcnchal0 liblinuxcncini1 libnml0 libposemath0 libpyplugin0 librs274-0 libtooldata0 # These are dlopened by rtapi_app, which is already linked against libc. linuxcnc-uspace: library-not-linked-against-libc [usr/lib/linuxcnc/modules/*.so] diff --git a/debian/linuxcnc.manpages.in b/debian/linuxcnc.manpages.in index 1b8eb01b38a..8c48a5eb1fc 100644 --- a/debian/linuxcnc.manpages.in +++ b/debian/linuxcnc.manpages.in @@ -30,6 +30,7 @@ usr/share/man/man1/hexagui.1 usr/share/man/man1/hy_gt_vfd.1 usr/share/man/man1/hy_vfd.1 usr/share/man/man1/image-to-gcode.1 +usr/share/man/man1/inivalue.1 usr/share/man/man1/inivar.1 usr/share/man/man1/io.1 usr/share/man/man1/iocontrol.1 diff --git a/docs/man/.gitignore b/docs/man/.gitignore index 8be76069d5a..d2c2102003d 100644 --- a/docs/man/.gitignore +++ b/docs/man/.gitignore @@ -39,6 +39,7 @@ man1/hexagui.1 man1/hy_gt_vfd.1 man1/hy_vfd.1 man1/image-to-gcode.1 +man1/inivalue.1 man1/inivar.1 man1/io.1 man1/iov2.1 diff --git a/docs/po4a.cfg b/docs/po4a.cfg index 7ea20fe7477..64d10d967a9 100644 --- a/docs/po4a.cfg +++ b/docs/po4a.cfg @@ -177,6 +177,7 @@ [type: AsciiDoc_def] src/man/man1/hy_gt_vfd.1.adoc $lang:src/$lang/man/man1/hy_gt_vfd.1.adoc [type: AsciiDoc_def] src/man/man1/hy_vfd.1.adoc $lang:src/$lang/man/man1/hy_vfd.1.adoc [type: AsciiDoc_def] src/man/man1/image-to-gcode.1.adoc $lang:src/$lang/man/man1/image-to-gcode.1.adoc +[type: AsciiDoc_def] src/man/man1/inivalue.1.adoc $lang:src/$lang/man/man1/inivalue.1.adoc [type: AsciiDoc_def] src/man/man1/inivar.1.adoc $lang:src/$lang/man/man1/inivar.1.adoc [type: AsciiDoc_def] src/man/man1/io.1.adoc $lang:src/$lang/man/man1/io.1.adoc [type: AsciiDoc_def] src/man/man1/latency-histogram.1.adoc $lang:src/$lang/man/man1/latency-histogram.1.adoc diff --git a/docs/src/config/ini-config.adoc b/docs/src/config/ini-config.adoc index e56d9dc7388..6ffc2b06906 100644 --- a/docs/src/config/ini-config.adoc +++ b/docs/src/config/ini-config.adoc @@ -24,7 +24,7 @@ Each end of line or newline character creates a new element. === Comments(((INI File,Components,Comments))) A comment line is started with a ; or a # mark. -When the INI reader sees either of these marks at the start a line, the rest of the line is ignored by the software. +When the INI reader sees either of these marks on a line, the rest of the line is ignored by the software. Comments can be used to describe what an INI element will do. [source,{ini}] @@ -45,14 +45,17 @@ DISPLAY = axis In this list, the DISPLAY variable will be set to axis because the other one is commented out. If someone carelessly edits a list like this and leaves two of the lines uncommented, the first one encountered will be used. -Note that inside a variable, the "#" and ";" characters do not denote comments: +Note that inside a variable's value, the "#" and ";" characters are still comments. +You should use double or single quoted strings if you need to embed # or ; characters: [source,{ini}] ---- -INCORRECT = value # and a comment +# Below results in INCORRECT=value +# because comments are stripped +INCORRECT = value # and a comment -# Correct Comment -CORRECT = value +# Correct embedding +CORRECT = "value # and an embedded # char" ---- [[sub:ini:sections]] @@ -62,6 +65,8 @@ Related parts of an INI file are separated into sections. A section name is enclosed in brackets like this: `[THIS_SECTION]`. The order of sections is unimportant. Sections begin at the section name and end at the next section name. +Section identifiers are case senstive and can only contain letters A-Z, a-z, digits 0-9 and underscore (_). +Additionally, section identifiers cannot start with a digit. The following sections are used by LinuxCNC: @@ -84,9 +89,9 @@ The following sections are used by LinuxCNC: === Variables(((INI File,Components,Variables))) A variable line is made up of a variable name, an equals sign (`=`), and a value. -Everything from the first non-white space character after the `=` up to the end of the line is passed as the value, -so you can embed spaces in string symbols if you want to or need to. -A variable name is often called a keyword. +A variable identifier is case sensitive and may only consist of letters A-Z, a-z, digits 0-9 and underscore (_). +Additionally, variable identifiers cannot start with a digit. +White space at the beginning of the line and after the variable identifier, up to the equals sign, is ignored. .Variable Example [source,{ini}] @@ -95,8 +100,7 @@ MACHINE = My Machine ---- A variable line may be extended to multiple lines with a terminal backslash (\) character. -A maximum of `MAX_EXTEND_LINES` (==20) are allowed. -There must be no whitespace following the trailing backslash character. +There may be white space following the trailing backslash character, but this is strongly discouraged. Section identifiers may not be extended to multiple lines. @@ -112,13 +116,87 @@ ini.1.max_velocity \ ini.2.max_velocity ---- -.Boolean Variables +A specific variable in a specific sections is often denoted in the documentation as [SECTION]VARIABLE. +This specification mirrors the same way they are specified in HAL files for expansion. -Boolean values can be on one of `TRUE`, `YES` or `1` for true/enabled and one of `FALSE`, `NO` or `0` for false/disabled. The case is ignored. +Variable values may be quoted to ensure proper space and special character embedding in literal or escaped forms. +Both single and double quoted values are allowed. +Mutiple quoted value segments are merged into one value. +Embedding quotes in quoted values must use the backslash \\ character to escape the embedded quote if it is the same kind as the enclosing quotes. +Double quotes values also support all common escape formats and full Unicode: -The following sections detail each section of the configuration file, using sample values for the configuration lines. +* control: \\[abfnrtv] +* octal: \\[0-2][0-7]{0,2} +* hex: \\x[0-9a-fA-F]{2} +* UTF-16: \\u[0-9a-fA-F]{4} +* UTF-32: \\U[0-9a-fA-F]{8} + +The resulting value is always converted into UTF-8 and checked for validity. +It is not allowed to embed NUL characters either literally or by using an escape. + +.Value Quote and Escape Example +[source,{ini}] +---- +STRING = "Hello World!" +STRING = 'Hello World Too!' + +# The following would become: Hello World! +STRING = "Hello" \ +" " \ +'World' \ +"!" + +EMBED = "Literal single ' in double" +EMBED = 'Literal double " in single' +EMBED = "Literal double \" in double" +EMBED = 'Literal single \' in single' + +SMILE = "\\370\\237\\230\\200 = πŸ˜€" +SMILE = "\\xf0\\x9f\\x98\\x80 = πŸ˜€" +SMILE = "\\ud83d\\ude00 = πŸ˜€" +SMILE = "\\U0001f600 = πŸ˜€" +---- + +Variables' value can have types associated when they are read by LinuxCNC. +The types are enforced by the INI file reader when the appropriate function calls are performed. +All values may always be read as 'string'. Conversion into other types has restrictions. +Possible types are: + +* 'string' - the value is taken as-is +* 'boolean' - only boolean words are accepted +* 'signed integer' - whole numbers in decimal, hexadecimal, octal or binary bases +* 'unsigned integer' - whole numbers in decimal, hexadecimal, octal or binary bases +* 'real' - floating point values (always using decimal point) +* 'enumeration' - a restricted set of keys to represent a value or function + +Signed and unsigned integers are read and converted as 64-bit numbers when marked as `s64` and `u64`. +Plain old 32-bit integers are marked `int` and are always signed. +The default number base is decimal. Alternative bases may be specified using a prefix. +The complete list of valid integer numbers: + +* \[0-9]+ - decimal +* 0x\[0-9A-Fa-f]+ - hexadecimal +* 0o\[0-7]+ - octal +* 0b\[01]+ - binary + +Signed integers may be preceded by a plus (+) or minus (-) sign, regardless number base. +Unsigned integers will generate a warning if they are preceded by a minus (-) sign upon conversion. + +Boolean values are case insensitive and allow the following words: + +* true/enabled - `TRUE`, `YES` `ON` or `1` +* false/disabled - `FALSE`, `NO` `OFF` or `0` + +Enumerations are a set of keywords defined by LinuxCNC and interpreted to mean a setting, value or functionality. +The exact values are declared in the individual variable description and the variables are marked with `enum`. +Most enumerations are interpreted without case. +It is noted in the variable description if case matters. + +Some LinuxCNC variables are special in that their format is interpreted as a multi-valued entry. +These variables have an appropriate description below of the format expected. Variables that are used by LinuxCNC must always use the section names and variable names as shown. +Any custom or private variables and sections are up to the user. [[sub:ini:custom]] === Custom Sections and Variables(((INI File,Components,Custom sections and variables))) @@ -162,6 +240,9 @@ The value stored in the variable must match the type specified by the component To use the custom variables in G-code, use the global variable syntax `#<_ini[section]variable>`. The following example shows a simple Z-axis touch-off routine for a router or mill using a probe plate. +Please note that G-code embedded ini variables are converted to upper case before they are searched in the INI file. +The `#<_ini[section]variable>` should be called [SECTION]VARIABLE in the INI file. + .G-code Example [source,{ngc}] ---- @@ -188,7 +269,7 @@ The filename can be specified as: * a file in the same directory as the INI file * a file located relative to the working directory * an absolute file name (starts with a /) -* a user-home-relative file name (starts with a ~) +* a user-home-relative file name (starts with a ~/) Multiple #INCLUDE directives are supported. @@ -202,27 +283,30 @@ Multiple #INCLUDE directives are supported. #INCLUDE ~/linuxcnc/myincludes/rs274ngc.inc ---- -The #INCLUDE directives are supported for one level of expansion only -- an included file may not include additional files. +The #INCLUDE directives are supported up to 16 levels -- an included file may include additional files up to 16 levels deep. +Recursive inclusion of files is detected and flagged as an error. The recommended file extension is '.inc'. Do _not_ use a file extension of '.ini' for included files. [[sec:ini:sections]] == INI File Sections(((INI File,Sections))) +Variables in each section have an associated type as denoted in parentheses after the variable. + [[sub:ini:sec:emc]] === [EMC] Section(((INI File,Sections,[EMC] Section))) -* `VERSION = 1.1` - The format version of this configuration. +* `VERSION = 1.1` - (string) The format version of this configuration. Any value other than 1.1 will cause the configuration checker to run and try to update the configuration to the new style joint axes type of configuration. -* `MACHINE = My Controller` - This is the name of the controller, which is printed out at the top of most graphical interfaces. +* `MACHINE = My Controller` - (string) This is the name of the controller, which is printed out at the top of most graphical interfaces. You can put whatever you want here as long as you make it a single line long. -* `DEBUG = 0` - Debug level 0 means no messages will be printed when LinuxCNC is run from a <>. +* `DEBUG = 0` - (u64) Debug level 0 means no messages will be printed when LinuxCNC is run from a <>. Debug flags are usually only useful to developers. See src/emc/nml_intf/debugflags.h for other settings. -* `RCS_DEBUG = 1` RCS debug messages to show. Print only errors (1) by default if EMC_DEBUG_RCS and EMC_DEBUG_RCS bits in +* `RCS_DEBUG = 1` (u64) RCS debug messages to show. Print only errors (1) by default if EMC_DEBUG_RCS and EMC_DEBUG_RCS bits in `DEBUG` are unset, otherwise print all (-1). Use this to select RCS debug messages. See src/libnml/rcs/rcs_print.hh for all MODE flags. -* `RCS_DEBUG_DEST = STDOUT` - how to output RCS_DEBUG messages (NULL, STDOUT, STDERR, FILE, LOGGER, MSGBOX). -* `RCS_MAX_ERR = -1` - Number after which RCS errors are not reported anymore (-1 = infinite). -* `NML_FILE = /usr/share/linuxcnc/linuxcnc.nml` - Set this if you want to use a non-default NML configuration file. +* `RCS_DEBUG_DEST = STDOUT` - (enum) how to output RCS_DEBUG messages (NULL, STDOUT, STDERR, FILE, LOGGER, MSGBOX). +* `RCS_MAX_ERR = -1` - (int) Number after which RCS errors are not reported anymore (-1 = infinite). +* `NML_FILE = /usr/share/linuxcnc/linuxcnc.nml` - (string) Set this if you want to use a non-default NML configuration file. [[sub:ini:sec:display]] === [DISPLAY] Section(((INI File,Sections,[DISPLAY] Section))) @@ -233,34 +317,34 @@ AXIS is an interface for use with normal computer and monitor, Touchy is for use GMOCCAPY can be used both ways and offers also many connections for hardware controls. Descriptions of the interfaces are in the Interfaces section of the User Manual. -* `DISPLAY = axis` - The file name of the executable providing the user interface to use. +* `DISPLAY = axis` - (string) The file name of the executable providing the user interface to use. Prominent valid options are (all in lower case): `axis`, `touchy`, `gmoccapy`, `gscreen`, `tklinuxcnc`, `qtvcp`, `qtvcp qtdragon` or `qtvcp qtplasmac`. -* `POSITION_OFFSET = RELATIVE` - The coordinate system (`RELATIVE` or `MACHINE`) to show on the DRO when the user interface starts. +* `POSITION_OFFSET = RELATIVE` - (enum) The coordinate system (`RELATIVE` or `MACHINE`) to show on the DRO when the user interface starts. The RELATIVE coordinate system reflects the G92 and G5__x__ coordinate offsets currently in effect. -* `POSITION_FEEDBACK = COMMANDED` - The coordinate value (`COMMANDED` or `ACTUAL`) to show on the DRO when the user interface starts. +* `POSITION_FEEDBACK = COMMANDED` - (enum) The coordinate value (`COMMANDED` or `ACTUAL`) to show on the DRO when the user interface starts. In AXIS this can be changed from the View menu. The COMMANDED position is the position requested by LinuxCNC. The ACTUAL position is the feedback position of the motors if they have feedback like most servo systems. Typically the COMMANDED value is used. -* `DRO_FORMAT_MM = %+08.6f` - Override the default DRO formatting in metric mode (normally 3 decimal places, padded with spaces to 6 digits to the left). +* `DRO_FORMAT_MM = %+08.6f` - (string) Override the default DRO formatting in metric mode (normally 3 decimal places, padded with spaces to 6 digits to the left). The example above will pad with zeros, display 6 decimal digits and force display of a + sign for positive numbers. Formatting follows Python practice: https://docs.python.org/2/library/string.html#format-specification-mini-language . An error will be raised if the format can not accept a floating-point value. -* `DRO_FORMAT_IN = % 4.1f` - Override the default DRO formatting in imperial mode (normally 4 decimal places, padded with spaces to 6 digits to the left). +* `DRO_FORMAT_IN = % 4.1f` - (string) Override the default DRO formatting in imperial mode (normally 4 decimal places, padded with spaces to 6 digits to the left). The example above will display only one decimal digit. Formatting follows Python practice: https://docs.python.org/2/library/string.html#format-specification-mini-language . An error will be raised if the format can not accept a floating-point value. -* `CONE_BASESIZE = .25` - Override the default cone/tool base size of .5 in the graphics display. Valid values are between 0.025 and 2.0. -* `DISABLE_CONE_SCALING = TRUE` - Any non-empty value (including "0") will override the default behavior of scaling the cone/tool size using the extents of the currently loaded G-code program in the graphics display. -* `MAX_FEED_OVERRIDE = 1.2` - The maximum feed override the user may select. +* `CONE_BASESIZE = .25` - (real) Override the default cone/tool base size of .5 in the graphics display. Valid values are between 0.025 and 2.0. +* `DISABLE_CONE_SCALING = TRUE` - (bool) Any non-empty value (including "0") will override the default behavior of scaling the cone/tool size using the extents of the currently loaded G-code program in the graphics display. +* `MAX_FEED_OVERRIDE = 1.2` - (real) The maximum feed override the user may select. 1.2 means 120% of the programmed feed rate. -* `MIN_SPINDLE_OVERRIDE = 0.5` - The minimum spindle override the user may select. +* `MIN_SPINDLE_OVERRIDE = 0.5` - (real) The minimum spindle override the user may select. 0.5 means 50% of the programmed spindle speed. (This is used to set the minimum spindle speed.) -* `MIN_SPINDLE_0_OVERRIDE = 0.5` - The minimum spindle override the user may select. +* `MIN_SPINDLE_0_OVERRIDE = 0.5` - (real) The minimum spindle override the user may select. 0.5 means 50% of the programmed spindle speed. (This is used to set the minimum spindle speed.) On multi spindle machine there will be entries for each spindle number. Only used by the QtVCP based user interfaces. -* `MAX_SPINDLE_OVERRIDE = 1.0` - The maximum spindle override the user may select. 1.0 means 100% of the programmed spindle speed. -* `MAX_SPINDLE_0_OVERRIDE = 1.0` - The maximum feed override the user may select. +* `MAX_SPINDLE_OVERRIDE = 1.0` - (real) The maximum spindle override the user may select. 1.0 means 100% of the programmed spindle speed. +* `MAX_SPINDLE_0_OVERRIDE = 1.0` - (real) The maximum feed override the user may select. 1.2 means 120% of the programmed feed rate. On multi spindle machine there will be entries for each spindle number. Only used by the QtVCP based user interfaces. * `DEFAULT_SPINDLE_SPEED = 100` - The default spindle RPM when the spindle is started in manual mode. @@ -562,7 +646,7 @@ The maximum number of `USER_M_PATH` directories is defined at compile time (typ: Chip breaking back-off distance in machine units * 'G83_PECK_CLEARANCE = .020' (default: Metric machine: 1mm, imperial machine: .050 inches) Clearance distance from last feed depth when machine rapids back to bottom of hole, in machine units. - + [NOTE] ==== The above six options were controlled by the `FEATURES` bitmask in versions of LinuxCNC prior to 2.8. @@ -581,7 +665,7 @@ FEATURES & 0x20 -> OWORD_WARNONLY [NOTE] `[WIZARD]WIZARD_ROOT` is a valid search path but the Wizard has not been fully implemented and the results of using it are unpredictable. -* `LOG_LEVEL = 0` +* `LOG_LEVEL = 0` Specify the log_level (default: 0) //FIXME: Inconsistent * `LOG_FILE = file-name.log` + @@ -654,9 +738,9 @@ Absolute paths are not recommended as their use may limit relocation of configur Explicit use of the LIB: prefix causes use of the system library HALFILE without searching the INI file directory. HALFILE items specify files that loadrt HAL components and make signal connections between component pins. -Common mistakes are +Common mistakes are: - . omission of the addf statement needed to add a component's function(s) to a thread, + . omission of the addf statement needed to add a component's function(s) to a thread, . incomplete signal (net) specifiers. Omission of required addf statements is almost always an error. @@ -704,7 +788,7 @@ For more information see the <> chapter. * `MDI_COMMAND = G53 G0 X0 Y0 Z0` - An MDI command can be executed by using `halui.mdi-command-00`. Increment the number for each command listed in the [HALUI] section. - It is also possible to start subroutines. `MDI_COMMAND = o CALL [#]` + It is also possible to start subroutines. `MDI_COMMAND = o CALL [#]` [[sub:ini:sec:applications]] === [APPLICATIONS] Section(((INI File,Sections,[APPLICATIONS] Section))) @@ -875,7 +959,7 @@ Finally, no amount of tweaking will speed up a tool path with lots of small, tig This can help on smaller machines without home switches. If using the Mesa resolver interface this file can be used to emulate absolute encoders and eliminate the need for homing (with no loss of accuracy). See the hostmot2 manpage for more details. -* `NO_FORCE_HOMING = 1` - The default behavior is for LinuxCNC to force the user to home the machine before any MDI command or a program is run. +* `NO_FORCE_HOMING = 1` - (bool) The default behavior is for LinuxCNC to force the user to home the machine before any MDI command or a program is run. Normally, only jogging is allowed before homing. For configurations using identity kinematics, setting `NO_FORCE_HOMING = 1` allows the user to make MDI moves and run programs without homing the machine first. Interfaces using identity kinematics without homing ability will need to have this option set to 1. @@ -914,27 +998,27 @@ LinuxCNC will not know your joint travel limits when using `NO_FORCE_HOMING = 1` The __ specifies one of: X Y Z A B C U V W -* `TYPE = LINEAR` - The type of this axis, either `LINEAR` or `ANGULAR`. +* `TYPE = LINEAR` - (enum) The type of this axis, either `LINEAR` or `ANGULAR`. Required if this axis is not a default axis type. The default axis types are X,Y,Z,U,V,W = LINEAR and A,B,C = ANGULAR. This setting is effective with the AXIS GUI but note that other GUI's may handle things differently. -* `MAX_VELOCITY = 1.2` - Maximum velocity for this axis in <> per second. -* `MAX_ACCELERATION = 20.0` - Maximum acceleration for this axis in machine units per second squared. -* `MAX_JERK = 0.0` - Maximum jerk for this axis in machine units per second cubed. +* `MAX_VELOCITY = 1.2` - (real) Maximum velocity for this axis in <> per second. +* `MAX_ACCELERATION = 20.0` - (real) Maximum acceleration for this axis in machine units per second squared. +* `MAX_JERK = 0.0` - (real) Maximum jerk for this axis in machine units per second cubed. Used when S-curve trajectory planning is enabled. When set to 0 (default), no per-axis jerk limiting is applied. -* `MIN_LIMIT = -1000` - (((MIN LIMIT))) The minimum limit (soft limit) for axis motion, in machine units. +* `MIN_LIMIT = -1000` - (real) (((MIN LIMIT))) The minimum limit (soft limit) for axis motion, in machine units. When this limit is exceeded, the controller aborts axis motion. The axis must be homed before `MIN_LIMIT` is in force. For a rotary axis (A,B,C typ) with unlimited rotation having no `MIN_LIMIT` for that axis in the `[AXIS_``]` section a value of -1e99 is used. -* `MAX_LIMIT = 1000` - (((MAX LIMIT))) The maximum limit (soft limit) for axis motion, in machine units. +* `MAX_LIMIT = 1000` - (real) (((MAX LIMIT))) The maximum limit (soft limit) for axis motion, in machine units. When this limit is exceeded, the controller aborts axis motion. The axis must be homed before MAX_LIMIT is in force. For a rotary axis (A,B,C typ) with unlimited rotation having no `MAX_LIMIT` for that axis in the `[AXIS_``]` section a value of 1e99 is used. -* `WRAPPED_ROTARY = 1` - When this is set to 1 for an ANGULAR axis the axis will move 0-359.999 degrees. +* `WRAPPED_ROTARY = 1` - (bool) When this is set to 1 for an ANGULAR axis the axis will move 0-359.999 degrees. Positive Numbers will move the axis in a positive direction and negative numbers will move the axis in the negative direction. -* `LOCKING_INDEXER_JOINT = 4` - This value selects a joint to use for a locking indexer for the specified axis __. +* `LOCKING_INDEXER_JOINT = 4` - (int) This value selects a joint to use for a locking indexer for the specified axis __. In this example, the joint is 4 which would correspond to the B axis for a XYZAB system with trivkins (identity) kinematics. When set, a G0 move for this axis will initiate an unlock with the `joint.4.unlock pin` then wait for the `joint.4.is-unlocked` pin then move the joint at the rapid rate for that joint. After the move the `joint.4.unlock` will be false and motion will wait for `joint.4.is-unlocked` to go false. @@ -950,7 +1034,7 @@ The jointmask bits are: (LSB)0:joint0, 1:joint1, 2:joint2, ... + Example: `loadrt motmod ... unlock_joints_mask=0x38` creates unlock-pins for joints 3,4,5. -* `OFFSET_AV_RATIO = 0.1` - If nonzero, this item enables the use of HAL input pins for external axis offsets: +* `OFFSET_AV_RATIO = 0.1` - (real) If nonzero, this item enables the use of HAL input pins for external axis offsets: + [source,{ini}] ---- @@ -990,22 +1074,22 @@ For example, using trivkins with `coordinates=XZ`, the joint-axes relationships For more information on kinematics modules see the manpage 'kins' (on the UNIX terminal type `man kins`). -* `TYPE = LINEAR` - The type of joint, either `LINEAR` or `ANGULAR`. -* `UNITS = INCH` - (((UNITS))) +* `TYPE = LINEAR` - (enum) The type of joint, either `LINEAR` or `ANGULAR`. +* `UNITS = INCH` - (enum) (((UNITS))) If specified, this setting overrides the related `[TRAJ] UNITS` setting, e.g., `[TRAJ]LINEAR_UNITS` if the `TYPE` of this joint is `LINEAR`, `[TRAJ]ANGULAR_UNITS` if the `TYPE` of this joint is `ANGULAR`. -* `MAX_VELOCITY = 1.2` - Maximum velocity for this joint in <> per second. -* `MAX_ACCELERATION = 20.0` - Maximum acceleration for this joint in machine units per second squared. -* `MAX_JERK = 0.0` - Maximum jerk for this joint in machine units per second cubed. +* `MAX_VELOCITY = 1.2` - (real) Maximum velocity for this joint in <> per second. +* `MAX_ACCELERATION = 20.0` - (real) Maximum acceleration for this joint in machine units per second squared. +* `MAX_JERK = 0.0` - (real) Maximum jerk for this joint in machine units per second cubed. Used when S-curve trajectory planning is enabled. When set to 0 (default), no per-joint jerk limiting is applied. -* `BACKLASH = 0.0000` - (((Backlash))) Backlash in machine units. +* `BACKLASH = 0.0000` - (real) (((Backlash))) Backlash in machine units. Backlash compensation value can be used to make up for small deficiencies in the hardware used to drive an joint. If backlash is added to an joint and you are using steppers the `STEPGEN_MAXACCEL` must be increased to 1.5 to 2 times the `MAX_ACCELERATION` for the joint. Excessive backlash compensation can cause an joint to jerk as it changes direction. If a COMP_FILE is specified for a joint BACKLASH is not used. // add a link to machine units -* `COMP_FILE =` _file.extension_ - (((Compensation))) +* `COMP_FILE =` _file.extension_ - (string) (((Compensation))) The compensation file consists of map of position information for the joint. Compensation file values are in machine units. Each set of values are are on one line separated by a space. @@ -1018,7 +1102,7 @@ For more information on kinematics modules see the manpage 'kins' (on the UNIX t + If `COMP_FILE` is specified for a joint, `BACKLASH` is not used. -* `COMP_FILE_TYPE = 0` or `1` - Specifies the type of compensation file. +* `COMP_FILE_TYPE = 0` or `1` - (int) Specifies the type of compensation file. The first value is the nominal (commanded) position for both types. + A `COMP_FILE_TYPE` must be specified for each `COMP_FILE`. ** 'Type 0:' The second value specifies the actual position as the joint is moving in the positive direction (increasing value). @@ -1041,11 +1125,11 @@ If `COMP_FILE` is specified for a joint, `BACKLASH` is not used. 1.000 0.003 -0.004 ---- -* `MIN_LIMIT = -1000` - (((MIN LIMIT))) +* `MIN_LIMIT = -1000` - (real) (((MIN LIMIT))) The minimum limit for joint motion, in machine units. When this limit is reached, the controller aborts joint motion. For a rotary joint with unlimited rotation having no `MIN_LIMIT` for that joint in the `[JOINT_N]` section a the value -1e99 is used. -* `MAX_LIMIT = 1000` - (((MAX LIMIT))) +* `MAX_LIMIT = 1000` - (real) (((MAX LIMIT))) The maximum limit for joint motion, in machine units. When this limit is reached, the controller aborts joint motion. For a rotary joint with unlimited rotation having no `MAX_LIMIT` for that joint in the `[JOINT_N]` section a the value 1e99 is used. @@ -1067,12 +1151,12 @@ The motion module always detects joint position limit violations and faults if t See also related https://github.com/LinuxCNC/linuxcnc/issues/97[GitHub issue #97]. ==== -* `MIN_FERROR = 0.010` - (((MIN FERROR))) +* `MIN_FERROR = 0.010` - (real) (((MIN FERROR))) This is the value in machine units by which the joint is permitted to deviate from commanded position at very low speeds. If MIN_FERROR is smaller than FERROR, the two produce a ramp of error trip points. You could think of this as a graph where one dimension is speed and the other is permitted following error. As speed increases the amount of following error also increases toward the `FERROR` value. -* `FERROR = 1.0` - (((FERROR))) `FERROR` is the maximum allowable following error, in machine units. +* `FERROR = 1.0` - (real) (((FERROR))) `FERROR` is the maximum allowable following error, in machine units. If the difference between commanded and sensed position exceeds this amount, the controller disables servo calculations, sets all the outputs to 0.0, and disables the amplifiers. If `MIN_FERROR` is present in the INI file, velocity-proportional following errors are used. Here, the maximum allowable following error is proportional to the speed, @@ -1080,48 +1164,48 @@ See also related https://github.com/LinuxCNC/linuxcnc/issues/97[GitHub issue #97 The maximum allowable following error will always be greater than `MIN_FERROR`. This prevents small following errors for stationary axes from inadvertently aborting motion. Small following errors will always be present due to vibration, etc. -* `LOCKING_INDEXER = 1` - Indicates the joint is used as a locking indexer. +* `LOCKING_INDEXER = 1` - (bool) Indicates the joint is used as a locking indexer. ==== Homing These parameters are Homing related, for a better explanation read the <> Chapter. -* `HOME = 0.0` - The position that the joint will go to upon completion of the homing sequence. -* `HOME_OFFSET = 0.0` - +* `HOME = 0.0` - (real) The position that the joint will go to upon completion of the homing sequence. +* `HOME_OFFSET = 0.0` - (real) The joint position of the home switch or index pulse, in <>. When the home point is found during the homing process, this is the position that is assigned to that point. When sharing home and limit switches and using a home sequence that will leave the home/limit switch in the toggled state, the home offset can be used define the home switch position to be other than 0 if your HOME position is desired to be 0. -* `HOME_SEARCH_VEL = 0.0` - (((HOME SEARCH VEL))) Initial homing velocity in machine units per second. +* `HOME_SEARCH_VEL = 0.0` - (real) (((HOME SEARCH VEL))) Initial homing velocity in machine units per second. Sign denotes direction of travel. A value of zero means assume that the current location is the home position for the machine. If your machine has no home switches you will want to leave this value at zero. -* `HOME_LATCH_VEL = 0.0` - +* `HOME_LATCH_VEL = 0.0` - (real) Homing velocity in machine units per second to the home switch latch position. Sign denotes direction of travel. -* `HOME_FINAL_VEL = 0.0` - +* `HOME_FINAL_VEL = 0.0` - (real) Velocity in machine units per second from home latch position to home position. If left at 0 or not included in the joint rapid velocity is used. Must be a positive number. -* `HOME_USE_INDEX = NO` - +* `HOME_USE_INDEX = NO` - (bool) If the encoder used for this joint has an index pulse, and the motion card has provision for this signal you may set it to yes. When it is yes, it will affect the kind of home pattern used. Currently, you can't home to index with steppers unless you're using StepGen in velocity mode and PID. -* `HOME_INDEX_NO_ENCODER_RESET = NO` - +* `HOME_INDEX_NO_ENCODER_RESET = NO` - (bool) Use YES if the encoder used for this joint does not reset its counter when an index pulse is detected after assertion of the joint `index_enable` HAL pin. Applicable only for `HOME_USE_INDEX = YES`. -* `HOME_IGNORE_LIMITS = NO` - +* `HOME_IGNORE_LIMITS = NO` - (bool) When you use the limit switch as a home switch and the limit switch this should be set to YES. When set to YES the limit switch for this joint is ignored when homing. You must configure your homing so that at the end of your home move the home/limit switch is not in the toggled state you will get a limit switch error after the home move. -* `HOME_IS_SHARED =` __ - - If the home input is shared by more than one joint set __ to 1 to prevent homing from starting if the one of the shared switches is already closed. - Set __ to 0 to permit homing if a switch is closed. -* `HOME_ABSOLUTE_ENCODER = 0` | `1` | `2` - Used to indicate the joint uses an absolute encoder. +* `HOME_IS_SHARED = NO` - (bool) + If the home input is shared by more than one joint set to true to prevent homing from starting if the one of the shared switches is already closed. + Set to false (the default) to permit homing if a switch is closed. +* `HOME_ABSOLUTE_ENCODER = 0` | `1` | `2` - (int) Used to indicate the joint uses an absolute encoder. At a request for homing, the current joint value is set to the `HOME_OFFSET` value. If the `HOME_ABSOLUTE_ENCODER` setting is 1, the machine makes the usual final move to the `HOME` value. If the `HOME_ABSOLUTE_ENCODER` setting is 2, no final move is made. -* `HOME_SEQUENCE =` __ - Used to define the "Home All" sequence. +* `HOME_SEQUENCE =` __ - (int) Used to define the "Home All" sequence. __ must start at `0` or `1` or `-1`. Additional sequences may be specified with numbers increasing by 1 (in absolute value). Skipping of sequence numbers is not allowed. @@ -1129,7 +1213,7 @@ These parameters are Homing related, for a better explanation read the <>. -* `VOLATILE_HOME = 0` - +* `VOLATILE_HOME = 0` - (bool) When enabled (set to `1`) this joint will be unhomed if the Machine Power is off or if E-Stop is on. This is useful if your machine has home switches and does not have position feedback such as a step and direction driven machine. @@ -1145,7 +1229,7 @@ For more information on custom INI file entries see the <>. +* `DEADBAND = 0.000015` - (real) How close is close enough to consider the motor in position, in <>. + -- This is often set to a distance equivalent to 1, 1.5, 2, or 3 encoder counts, but there are no strict rules. @@ -1343,41 +1427,41 @@ You change these default by setting the following INI variables: [NOTE] These settings are for the motion controller component. -Control screens can limit these settings further. +Control screens can limit these settings further. -* `MAX_FORWARD_VELOCITY = 20000` +* `MAX_FORWARD_VELOCITY = 20000` - (real) The maximum spindle speed (in rpm) for the specified spindle. Optional. This will also set MAX_REVERSE_VELOCITY to the negative value unless overridden. -* `MIN_FORWARD_VELOCITY = 3000` +* `MIN_FORWARD_VELOCITY = 3000` - (real) The minimum spindle speed (in rpm) for the specified spindle. Optional. Many spindles have a minimum speed below which they should not be run. Any spindle speed command below this limit will be /increased/ to this limit. -* `MAX_REVERSE_VELOCITY = 20000` +* `MAX_REVERSE_VELOCITY = 20000` - (real) This setting will default to `MAX_FORWARD_VELOCITY` if omitted. It can be used in cases where the spindle speed is limited in reverse. Set to zero for spindles which must not be run in reverse. In this context "max" refers to the absolute magnitude of the spindle speed. -* `MIN_REVERSE_VELOCITY = 3000`` +* `MIN_REVERSE_VELOCITY = 3000` - (real) This setting is equivalent to `MIN_FORWARD_VELOCITY` but for reverse spindle rotation. It will default to the MIN_FORWARD_VELOCITY if omitted. -* `INCREMENT = 200` +* `INCREMENT = 200` - (real) Sets the step size for spindle speed increment / decrement commands. This can have a different value for each spindle. This setting is effective with AXIS and Touchy but note that some control screens may handle things differently. -* `HOME_SEARCH_VELOCITY = 100` - FIXME: Spindle homing not yet working. +* `HOME_SEARCH_VELOCITY = 100` - (real) FIXME: Spindle homing not yet working. Sets the homing speed (rpm) for the spindle. The spindle will rotate at this velocity during the homing sequence until the spindle index is located, at which point the spindle position will be set to zero. Note that it makes no sense for the spindle home position to be any value other than zero, and so there is no provision to do so. -* `HOME_SEQUENCE = 0` - FIXME: Spindle homing not yet working +* `HOME_SEQUENCE = 0` - (int) FIXME: Spindle homing not yet working Controls where in the general homing sequence the spindle homing rotations occur. Set the `HOME_SEARCH_VELOCITY` to zero to avoid spindle rotation during the homing sequence. [[sub:ini:sec:emcio]] === [EMCIO] Section(((INI File,Sections,[EMCIO] Section))) -* `TOOL_TABLE = tool.tbl` - The file which contains tool information, described in the User Manual. -* `DB_PROGRAM = db_program` - Path to an executable program that manages tool data. +* `TOOL_TABLE = tool.tbl` - (string) The file which contains tool information, described in the User Manual. +* `DB_PROGRAM = db_program` - (string) Path to an executable program that manages tool data. When a DB_PROGRAM is specified, a TOOL_TABLE entry is ignored. * `TOOL_CHANGE_POSITION = 0 0 2` - Specifies the XYZ location to move to when performing a tool change if three digits are used. @@ -1385,16 +1469,16 @@ Control screens can limit these settings further. Specifies the XYZABCUVW location when 9 digits are used. Tool Changes can be combined. For example if you combine the quill up with change position you can move the Z first then the X and Y. -* `TOOL_CHANGE_WITH_SPINDLE_ON = 1` - +* `TOOL_CHANGE_WITH_SPINDLE_ON = 1` - (bool) The spindle will be left on during the tool change when the value is 1. Useful for lathes or machines where the material is in the spindle, not the tool. -* `TOOL_CHANGE_QUILL_UP = 1` - +* `TOOL_CHANGE_QUILL_UP = 1` - (bool) The Z axis will be moved to machine zero prior to the tool change when the value is 1. This is the same as issuing a `G0 G53 Z0`. -* `TOOL_CHANGE_AT_G30 = 1` - +* `TOOL_CHANGE_AT_G30 = 1` - (bool) The machine is moved to reference point defined by parameters 5181-5186 for G30 if the value is 1. For more information see <> and <>. -* `RANDOM_TOOLCHANGER = 1` - +* `RANDOM_TOOLCHANGER = 1` - (bool) This is for machines that cannot place the tool back into the pocket it came from. For example, machines that exchange the tool in the active pocket with the tool in the spindle. diff --git a/docs/src/config/python-interface.adoc b/docs/src/config/python-interface.adoc index 11a3f2ba29c..cdf5a3d97e3 100644 --- a/docs/src/config/python-interface.adoc +++ b/docs/src/config/python-interface.adoc @@ -871,7 +871,7 @@ inifile = linuxcnc.ini(sys.argv[1]) # inifile.find() returns None if the key wasn't found - the # following idiom is useful for setting a default value: -machine_name = inifile.find("EMC", "MACHINE") or "unknown" +machine_name = inifile.getstring("EMC", "MACHINE", fallback="unknown") print("machine name: ", machine_name) # inifile.findall() returns a list of matches, or an empty list @@ -881,9 +881,19 @@ extensions = inifile.findall("FILTER", "PROGRAM_EXTENSION") print("extensions: ", extensions) # override default NML file by INI parameter if given -nmlfile = inifile.find("EMC", "NML_FILE") +nmlfile = inifile.getstring("EMC", "NML_FILE", fallback="") if nmlfile: linuxcnc.nmlfile = os.path.join(os.path.dirname(sys.argv[1]), nmlfile) + +# Other examples: +realval = inifile.getreal("AXIS_X", "MAX_VELOCITY", fallback=5.0) +boolval = inifile.getbool("JOINT_0", "HOME_USE_INDEX", fallback=False) + +# None is returned without fallback= if the variable was not found +intval = inifile.getint( "KINS", "JOINTS") +if None == intval: + print("Error: [KINS]JOINTS not defined or an invalid integer" + ---- Or for the same INI file as LinuxCNC: diff --git a/docs/src/gui/gladevcp-libraries.adoc b/docs/src/gui/gladevcp-libraries.adoc index f961c785b02..7e60b0102f6 100644 --- a/docs/src/gui/gladevcp-libraries.adoc +++ b/docs/src/gui/gladevcp-libraries.adoc @@ -45,14 +45,14 @@ MACHINE_IS_METRIC = False MACHINE_UNIT_CONVERSION = 1 MACHINE_UNIT_CONVERSION_9 = [1]*9 TRAJ_COORDINATES = -JOINT_COUNT = int(self.INI.find("KINS","JOINTS")or 0) +JOINT_COUNT = self.INI.getint("KINS","JOINTS", fallback=0) AVAILABLE_AXES = ['X','Y','Z'] AVAILABLE_JOINTS = [0,1,2] GET_NAME_FROM_JOINT = {0:'X',1:'Y',2:'Z'} GET_JOG_FROM_NAME = {'X':0,'Y':1,'Z':2} NO_HOME_REQUIRED = False HOME_ALL_FLAG -JOINT_TYPE = self.INI.find(section, "TYPE") or "LINEAR" +JOINT_TYPE = self.INI.getstring(section, "TYPE", fallback="LINEAR") JOINT_SEQUENCE_LIST JOINT_SYNC_LIST @@ -71,7 +71,7 @@ MAX_ANGULAR_JOG_VEL = MAX_FEED_OVERRIDE = MAX_TRAJ_VELOCITY = -AVAILABLE_SPINDLES = int(self.INI.find("TRAJ", "SPINDLES") or 1) +AVAILABLE_SPINDLES = self.INI.getint("TRAJ", "SPINDLES", fallback=1) DEFAULT_SPINDLE_0_SPEED = 200 MAX_SPINDLE_0_SPEED = 2500 MAX_SPINDLE_0_OVERRIDE = 100 @@ -89,7 +89,7 @@ USRMESS_DETAILS = self.INI.findall("DISPLAY", "MESSAGE_DETAILS") USRMESS_ICON = self.INI.findall("DISPLAY", "MESSAGE_ICON") ZIPPED_USRMESS = -self.GLADEVCP = (self.INI.find("DISPLAY", "GLADEVCP")) or None +self.GLADEVCP = self.INI.find("DISPLAY", "GLADEVCP") # embedded program info TAB_NAMES = (self.INI.findall("DISPLAY", "EMBED_TAB_NAME")) or None diff --git a/docs/src/gui/gladevcp.adoc b/docs/src/gui/gladevcp.adoc index 92a93a991f0..9ac258e0007 100644 --- a/docs/src/gui/gladevcp.adoc +++ b/docs/src/gui/gladevcp.adoc @@ -2693,7 +2693,7 @@ class HandlerClass: self.max_value.connect('value-changed', self._on_max_value_change) inifile = linuxcnc.ini(os.getenv("INI_FILE_NAME")) - mmin = float(inifile.find("METER", "MIN") or 0.0) + mmin = inifile.getreal("METER", "MIN", fallback=0.0) self.meter = self.builder.get_object('meter') self.meter.min = mmin diff --git a/docs/src/gui/qtvcp-libraries.adoc b/docs/src/gui/qtvcp-libraries.adoc index 02761b5a25f..c175c95e866 100644 --- a/docs/src/gui/qtvcp-libraries.adoc +++ b/docs/src/gui/qtvcp-libraries.adoc @@ -112,14 +112,14 @@ MACHINE_IS_METRIC = False MACHINE_UNIT_CONVERSION = 1 MACHINE_UNIT_CONVERSION_9 = [1]*9 TRAJ_COORDINATES = -JOINT_COUNT = int(self.INI.find("KINS","JOINTS")or 0) +JOINT_COUNT = self.INI.getint("KINS","JOINTS", fallback=0) AVAILABLE_AXES = ['X','Y','Z'] AVAILABLE_JOINTS = [0,1,2] GET_NAME_FROM_JOINT = {0:'X',1:'Y',2:'Z'} GET_JOG_FROM_NAME = {'X':0,'Y':1,'Z':2} NO_HOME_REQUIRED = False HOME_ALL_FLAG -JOINT_TYPE = self.INI.find(section, "TYPE") or "LINEAR" +JOINT_TYPE = self.INI.getstring(section, "TYPE", fallback="LINEAR") JOINT_SEQUENCE_LIST JOINT_SYNC_LIST @@ -138,7 +138,7 @@ MAX_ANGULAR_JOG_VEL = MAX_FEED_OVERRIDE = MAX_TRAJ_VELOCITY = -AVAILABLE_SPINDLES = int(self.INI.find("TRAJ", "SPINDLES") or 1) +AVAILABLE_SPINDLES = self.INI.getint("TRAJ", "SPINDLES", fallback=1) DEFAULT_SPINDLE_0_SPEED = 200 MAX_SPINDLE_0_SPEED = 2500 MAX_SPINDLE_0_OVERRIDE = 100 @@ -160,7 +160,7 @@ USRMESS_DETAILS = self.INI.findall("DISPLAY", "MESSAGE_DETAILS") USRMESS_ICON = self.INI.findall("DISPLAY", "MESSAGE_ICON") ZIPPED_USRMESS = -self.GLADEVCP = (self.INI.find("DISPLAY", "GLADEVCP")) or None +self.GLADEVCP = self.INI.find("DISPLAY", "GLADEVCP") ---- === Embedded program info diff --git a/docs/src/man/man1/inivalue.1.adoc b/docs/src/man/man1/inivalue.1.adoc new file mode 100644 index 00000000000..56e4cc25836 --- /dev/null +++ b/docs/src/man/man1/inivalue.1.adoc @@ -0,0 +1,157 @@ += inivalue(1) + +== NAME + +inivalue - Query an INI file + +== SYNOPSIS + +*inivalue* [**--var**=_variable_] [**--sec**=_section_] [...options...] _INIFILE_ + +*inivalue* **--variables** [**--sec**=_section_] [...options...] _INIFILE_ + +*inivalue* **--sections** _INIFILE_ + +== DESCRIPTION + +Prints to stdout the result of a variable-in-section search in _INIFILE_, useful for +scripts that want to pick things out of INI files. + +The search for a variable can be limited by specifying the name of the _section_ +to search with the *--sec* option the name of the _variable_ to search with +the *--var* option. Neither are required and the program will simply find the +first matching entry. + +A list of variable's content, including optional section name prefix and variable +name may be obtained with the *--variables* option. The list of sections present +in _INIFILE_ can be extracted with the *--sections* option. + +== OPTIONS + +*-V*,*--var* _variable_:: + The variable to search for, if multiple matches exists and *-num* is not + specified, the first match is returned. + +*-S*,*-sec* _section_:: + The section to search in, if omitted, all sections are searched. + +*-N*,*--num* _occurrence_number_:: + The _occurrence_number_ specifies which instance of the _variable_ within + the _INIFILE_, and _section_ if provided, should be returned. If omitted, + the first matching occurrence is returned. + +*-a*,*--all*:: + Print all matching variables instead of the first matching or the num'th + matching. The *--all* option has precedence over the *--num* option. + +*-y*,*--type* _cvttype_:: + Perform type-conversion after the search has been matched. Any errors in + conversion will be flagged and print an error message. The _cvttype_ + can be one of i[nteger], u[nsigned], r[eal], s[tring] or b[oolean]. It + defaults to string if not specified. + +*-T*,*--tildeexpand*:: + Replace the tilde in a value containing '~/pathname' with the expansion of + the 'HOME' environment variable, as in '$HOME/pathname'. Only relevant for + searches that match a single value of string type. + +*-m*,*--minimum* _value_:: + Check the converted value against _value_ in integer, unsigned or real type + conversions (see *--type*). The variable must be larger than or equal _value_. + +*-M*,*--maximum* _value_:: + Check the converted value against _value_ in integer, unsigned or real type + conversions (see *--type*). The variable must be less than or equal _value_. + +*-b*,*--boolnum*:: + Print boolean values (*--type*=b) using '0' for false and '1' for true instead + of 'true' and 'false'. + +*-B*,*--boolpy*:: + Print boolean values (*--type*=b) using 'False' for false and 'True' for true + instead of 'true' and 'false'. + +*-q*,*--quiet*:: + Do not print an error message when a variable is not found. The exit value is + still 2 if no entry is found, regardless the setting of this option. + +*-e*,*--sections*:: + Print all section names found in the INI-file. This option had highest + precedence. + +*-o*,*--variables*:: + Print all variable names found in the INI-file. You can limit the matched list + by specifying a section with *--sec* option. This option has second highest + precedence after *--sections*. + +*-c*,*--content*:: + Print the content of a variable along with the variable's name in the + form "VARNAME=value". This is only relevant when the *--variables* option is + specified. This can be combined with the *--prefix* option. + +*-p*,*--prefix*:: + Prefix the variable variable's name with the section's name in the + form "[SECTION]VARNAME". This is only relevant when the *--variables* option + is specified. This can be combined with the *--content* option. + +== INI-FILE FORMAT + +The INI-file format uses the common standard for INI-files with a few additions +and exceptions. + +Section names and variable names may only consist of letters a-z, A-Z, digits +0-9 and underscore (_). The first character in the name cannot be a digit. + +Comments are specified with the # or ; character and everything following +either # or ; is ignored until the end of the line. Embedding a # or ; in a +value can only be done if the value is single or double quoted. + +Values can be free form text or in single or double quoted form. The double +quoted form supports standard escapes and octal, hex, UTF-16 and UTF-32 escape +sequences. Multiple quoted segments are concatenated into one value. All values +are converted into UTF-8 and values are checked to be valid UTF-8. + +Line continuation may be performed by adding a \ to the end of the line. A +continuation merges the current line with the following line. + +Ini-files can include other INI-files using the '#INCLUDE filepath' directive. +It must start in the first column and the 'filepath' following the directive is +read as if it was text present in the original file. Include files can be nested +16 levels deep. + +== EXIT STATUS + +*0*:: + Success, a matching value has been printed. + +*1*:: + An error in the arguments, a missing INI-file name, an error while parsing + the INI-file or an error while performing tilde expansion (missing HOME + environment variable). + +*2*:: + The searched _section_, _variable_ and _num_'th entry was not found. + +*3*:: + The matched value failed the minimum/maximum test and was determined to be + outside the requested range. + +== BUGS + +None known at this time. + +== AUTHOR + +This man page written by B.Stultiens, as part of the LinuxCNC project. + +== REPORTING BUGS + +Report bugs at https://github.com/LinuxCNC/linuxcnc/issues. + +== COPYRIGHT + +Copyright (c) 2026 B.Stultiens. + +This is free software; see the source for copying conditions. There +is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. diff --git a/docs/src/man/man1/inivar.1.adoc b/docs/src/man/man1/inivar.1.adoc index b3ab9c90ab8..2715f1b2ac7 100644 --- a/docs/src/man/man1/inivar.1.adoc +++ b/docs/src/man/man1/inivar.1.adoc @@ -12,6 +12,9 @@ inivar - Query an INI file == DESCRIPTION +This program has been superseded by **inivalue**(1). The *inivar* program is +now a script that translates the options and invokes inivalue. + Prints to stdout the INI file result of a variable-in-section search, useful for scripts that want to pick things out of INI files. @@ -54,6 +57,10 @@ _section_ will be returned. None known at this time. +== SEE ALSO + +*inivalue*(1). + == AUTHOR This man page written by Andy Pugh, as part of the LinuxCNC project. diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index a90e0790c09..3ef8791bb2a 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -29,13 +29,13 @@ try: inifile = linuxcnc.ini(os.environ['INI_FILE_NAME']) trajcoordinates = inifile.find("TRAJ", "COORDINATES").lower().replace(" ", "") - jointcount = int(inifile.find("KINS", "JOINTS")) + jointcount = inifile.getint("KINS", "JOINTS") except: pass try: # get cycle time which could be in ms or seconds # convert to ms - use this to set update time - ct = float(inifile.find('DISPLAY', 'CYCLE_TIME') or 100) + ct = inifile.getreal('DISPLAY', 'CYCLE_TIME', fallback=100) if ct < 1: CYCLE_TIME = int(ct * 1000) else: diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index 8cdabb3a291..280898ce62f 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -1,7 +1,6 @@ import os import linuxcnc import collections -import configparser # Set up logging from . import logger @@ -57,39 +56,31 @@ def __init__(self, ini=None): self.update() def update(self): - - # use configParser so we can iter thru header - self.parser = configparser.RawConfigParser(strict=False) - self.parser.optionxform = str - try: - self.parser.read(filenames=self.INIPATH) - except: - pass - - ct = float(self.INI.find('DISPLAY', 'CYCLE_TIME') or 100) # possibly in seconds or ms + ct = self.INI.getreal('DISPLAY', 'CYCLE_TIME', fallback=100) # possibly in seconds or ms if ct < 1: self.CYCLE_TIME = int(ct * 1000) else: self.CYCLE_TIME = int(ct) - self.GRAPHICS_CYCLE_TIME =int(self.INI.find('DISPLAY', 'GRAPHICS_CYCLE_TIME') or 100) # in seconds - self.HALPIN_CYCLE_TIME = int(self.INI.find('DISPLAY', 'HALPIN_CYCLE_TIME') or 100) # in seconds - self.MDI_HISTORY_PATH = self.INI.find('DISPLAY', 'MDI_HISTORY_FILE') or '~/.axis_mdi_history' - self.QTVCP_LOG_HISTORY_PATH = self.INI.find('DISPLAY', 'LOG_FILE') or '~/qtvcp.log' - self.MACHINE_LOG_HISTORY_PATH = self.INI.find('DISPLAY', 'MACHINE_LOG_PATH') or '~/.machine_log_history' - self.PREFERENCE_PATH = self.INI.find("DISPLAY", "PREFERENCE_FILE_PATH") or None + self.GRAPHICS_CYCLE_TIME = self.INI.getint('DISPLAY', 'GRAPHICS_CYCLE_TIME', fallback=100) # in seconds + self.HALPIN_CYCLE_TIME = self.INI.getint('DISPLAY', 'HALPIN_CYCLE_TIME', fallback=100) # in seconds + self.MDI_HISTORY_PATH = self.INI.getstring('DISPLAY', 'MDI_HISTORY_FILE', fallback='~/.axis_mdi_history') + self.QTVCP_LOG_HISTORY_PATH = self.INI.getstring('DISPLAY', 'LOG_FILE', fallback='~/qtvcp.log') + self.MACHINE_LOG_HISTORY_PATH = self.INI.getstring('DISPLAY', 'MACHINE_LOG_PATH', fallback='~/.machine_log_history') + self.PREFERENCE_PATH = self.INI.getstring("DISPLAY", "PREFERENCE_FILE_PATH") # or None self.PROGRAM_PREFIX = self.get_error_safe_setting("DISPLAY", "PROGRAM_PREFIX", '~/linuxcnc/nc_files') + if not os.path.exists(os.path.expanduser(self.PROGRAM_PREFIX)): LOG.warning('Path not valid in INI File [DISPLAY] PROGRAM_PREFIX section') - temp = self.INI.find("DISPLAY", "USER_COMMAND_FILE") + temp = self.INI.getstring("DISPLAY", "USER_COMMAND_FILE") if not temp is None: self.USER_COMMAND_FILE = os.path.expanduser(temp) else: self.USER_COMMAND_FILE = None - self.STARTUP_CODES = (self.INI.find('RS274NGC', 'RS274NGC_STARTUP_CODE') ) or None + self.STARTUP_CODES = self.INI.getstring('RS274NGC', 'RS274NGC_STARTUP_CODE') # or None - self.SUB_PATH = (self.INI.find("RS274NGC", "SUBROUTINE_PATH")) or None + self.SUB_PATH = self.INI.getstring("RS274NGC", "SUBROUTINE_PATH") # or None if self.SUB_PATH is not None: for mpath in (self.SUB_PATH.split(':')): self.SUB_PATH_LIST.append(mpath) @@ -100,21 +91,21 @@ def update(self): else: self.MACRO_PATH = None - self.USER_M_PATH = (self.INI.find("RS274NGC", "USER_M_PATH")) or None + self.USER_M_PATH = self.INI.getstring("RS274NGC", "USER_M_PATH") # or None if self.USER_M_PATH is not None: for mpath in (self.USER_M_PATH.split(':')): self.USER_M_PATH_LIST.append(mpath) self.INI_MACROS = self.INI.findall("DISPLAY", "MACRO") - self.NGC_SUB_PATH = (self.INI.find("DISPLAY","NGCGUI_SUBFILE_PATH")) or None + self.NGC_SUB_PATH = self.INI.getstring("DISPLAY","NGCGUI_SUBFILE_PATH") # or None if not self.NGC_SUB_PATH is None: self.NGC_SUB_PATH = os.path.expanduser(self.NGC_SUB_PATH) - self.NGC_SUB = (self.INI.findall("DISPLAY", "NGCGUI_SUBFILE")) or None + self.NGC_SUB = self.INI.findall("DISPLAY", "NGCGUI_SUBFILE") or None - self.MACHINE_IS_LATHE = bool(self.INI.find("DISPLAY", "LATHE")) + self.MACHINE_IS_LATHE = self.INI.getbool("DISPLAY", "LATHE", fallback=False) try: - self.MACHINE_IS_QTPLASMAC = 'qtplasmac' in self.INI.find("DISPLAY", "DISPLAY") + self.MACHINE_IS_QTPLASMAC = 'qtplasmac' in self.INI.getstring("DISPLAY", "DISPLAY") except: self.MACHINE_IS_QTPLASMAC = False extensions = self.INI.findall("FILTER", "PROGRAM_EXTENSION") @@ -122,19 +113,19 @@ def update(self): self.PROGRAM_FILTERS_EXTENSIONS = self.get_filters_extensions() self.VALID_PROGRAM_EXTENSIONS = self.get_all_valid_extensions() - self.PARAMETER_FILE = (self.INI.find("RS274NGC", "PARAMETER_FILE")) or None + self.PARAMETER_FILE = self.INI.getstring("RS274NGC", "PARAMETER_FILE") # or None if self.PARAMETER_FILE is None and self.LINUXCNC_IS_RUNNING: LOG.critical('Missing PARAMETER_FILE setting in RS274NGC section') try: # check the INI file if UNITS are set to mm" # first check the global settings - units = self.INI.find("TRAJ", "LINEAR_UNITS") + units = self.INI.getstring("TRAJ", "LINEAR_UNITS") if units is None: if self.LINUXCNC_IS_RUNNING: LOG.critical('Missing LINEAR_UNITS in TRAJ, guessing units for machine from JOINT 0') # else then guess; The joint 0 is usually X axis - units = self.INI.find("JOINT_0", "UNITS") + units = self.INI.getstring("JOINT_0", "UNITS") if units is None: if self.LINUXCNC_IS_RUNNING: LOG.critical('Missing UNITS in JOINT_0, assuming metric based machine') @@ -157,9 +148,9 @@ def update(self): self.MACHINE_UNIT_CONVERSION_10 = [25.4] * 3 + [1] * 3 + [25.4] * 3 + [1] LOG.debug('Machine is IMPERIAL based. unit Conversion constant={}'.format(self.MACHINE_UNIT_CONVERSION)) - axes = self.INI.find("TRAJ", "COORDINATES") + axes = self.INI.getstring("TRAJ", "COORDINATES") try: - self.trajcoordinates = self.INI.find("TRAJ", "COORDINATES").lower().replace(" ","") + self.trajcoordinates = self.INI.getstring("TRAJ", "COORDINATES").lower().replace(" ","") except: self.trajcoordinates ='xyz' @@ -199,11 +190,11 @@ def update(self): self.AVAILABLE_JOINTS.append(num) # AXIS sanity check - av = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_VELOCITY') or None - aa = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_ACCELERATION') or None + av = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_VELOCITY') # or None + aa = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_ACCELERATION') # or None if av is None or aa is None: # some lathe configs have dummy Y axis for axis rotation G code - if letter == "Y" and self.MACHINE_IS_LATHE: + if letter.upper() == "Y" and self.MACHINE_IS_LATHE: pass else: LOG.critical( @@ -219,7 +210,7 @@ def update(self): self.GET_AXIS_INDEX_FROM_JOINT_NUM[int(i)] = int(axisnum) self.GET_JOINT_NUM_FROM_AXIS_INDEX[int(axisnum)] = int(i) - self.NO_HOME_REQUIRED = int(self.INI.find("TRAJ", "NO_FORCE_HOMING") or 0) + self.NO_HOME_REQUIRED = self.INI.getint("TRAJ", "NO_FORCE_HOMING", fallback=0) # home all check self.HOME_ALL_FLAG = 1 @@ -227,11 +218,11 @@ def update(self): jointcount = len(self.AVAILABLE_JOINTS) self.JOINT_SEQUENCE_LIST = {} for j in range(jointcount): - seq = self.INI.find("JOINT_" + str(j), "HOME_SEQUENCE") + seq = self.INI.getint("JOINT_" + str(j), "HOME_SEQUENCE") if seq is None: seq = 0 self.HOME_ALL_FLAG = 0 - self.JOINT_SEQUENCE_LIST[j] = int(seq) + self.JOINT_SEQUENCE_LIST[j] = seq # joint sequence/type self.JOINT_TYPE = [None] * jointcount self.JOINT_TYPE_INT = [None] * jointcount @@ -245,7 +236,7 @@ def update(self): else: self.JOINT_TYPE_INT[j] = 2 self.HAS_ANGULAR_JOINT = True - self.JOINT_SEQUENCE[j] = int(self.INI.find(section, "HOME_SEQUENCE") or 0) + self.JOINT_SEQUENCE[j] = self.INI.getint(section, "HOME_SEQUENCE", fallback=0) # jog synchronized sequence # gives a list of joints combined to make an axis @@ -336,12 +327,12 @@ def update(self): self.TRAJ_COORDINATES = temp.lower().replace(" ", "") else: self.TRAJ_COORDINATES = None - self.JOINT_COUNT = int(self.INI.find("KINS", "JOINTS") or 0) + self.JOINT_COUNT = self.INI.getint("KINS", "JOINTS", fallback=0) # check for weird kinematics like robots self.IS_TRIVIAL_MACHINE = bool('trivkins' in self.get_error_safe_setting("KINS", "KINEMATICS",'trivial')) - kinsmodule = self.INI.find("KINS", "KINEMATICS") or 'trivkins' + kinsmodule = self.INI.getstring("KINS", "KINEMATICS", fallback='trivkins') if kinsmodule.split()[0] == "trivkins": self.trivkinscoords = "XYZABCUVW" for item in kinsmodule.split(): @@ -349,57 +340,63 @@ def update(self): self.trivkinscoords = item.split("=")[1].upper() safe = 25 if self.MACHINE_IS_METRIC else 1 - self.DEFAULT_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "DEFAULT_LINEAR_VELOCITY", safe)) * 60 - self.MIN_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MIN_LINEAR_VELOCITY", 0)) * 60 + self.DEFAULT_LINEAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "DEFAULT_LINEAR_VELOCITY", safe) * 60 + self.MIN_LINEAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MIN_LINEAR_VELOCITY", 0) * 60 safe = 125 if self.MACHINE_IS_METRIC else 5 - self.MAX_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MAX_LINEAR_VELOCITY", safe)) * 60 + self.MAX_LINEAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MAX_LINEAR_VELOCITY", safe) * 60 LOG.debug('DEFAULT_LINEAR_VELOCITY = {}'.format(self.DEFAULT_LINEAR_JOG_VEL)) LOG.debug('MIN_LINEAR_VELOCITY = {}'.format(self.MIN_LINEAR_JOG_VEL)) LOG.debug('MAX_LINEAR_VELOCITY = {}'.format(self.MAX_LINEAR_JOG_VEL)) - self.DEFAULT_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "DEFAULT_ANGULAR_VELOCITY", 6)) * 60 - self.MIN_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MIN_ANGULAR_VELOCITY", 0)) * 60 - self.MAX_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MAX_ANGULAR_VELOCITY", 60)) * 60 + self.DEFAULT_ANGULAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "DEFAULT_ANGULAR_VELOCITY", 6) * 60 + self.MIN_ANGULAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MIN_ANGULAR_VELOCITY", 0) * 60 + self.MAX_ANGULAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MAX_ANGULAR_VELOCITY", 60) * 60 LOG.debug('DEFAULT_ANGULAR_VELOCITY = {}'.format(self.DEFAULT_ANGULAR_JOG_VEL)) LOG.debug('MIN_ANGULAR_VELOCITY = {}'.format(self.MIN_ANGULAR_JOG_VEL)) LOG.debug('MAX_ANGULAR_VELOCITY = {}'.format(self.MAX_ANGULAR_JOG_VEL)) - self.AVAILABLE_SPINDLES = int(self.INI.find("TRAJ", "SPINDLES") or 1) - self.SPINDLE_INCREMENT = int(self.INI.find("DISPLAY", "SPINDLE_INCREMENT") or 100) + self.AVAILABLE_SPINDLES = self.INI.getint("TRAJ", "SPINDLES", fallback=1) + self.SPINDLE_INCREMENT = self.INI.getint("DISPLAY", "SPINDLE_INCREMENT", fallback=100) +# FIXME:vvvvv +# FIXME: Aren't SPEED values real values? Velocity values are. for i in range(0, self.AVAILABLE_SPINDLES): - self['DEFAULT_SPINDLE_{}_SPEED'.format(i)] = int( - self.get_error_safe_setting("DISPLAY", "DEFAULT_SPINDLE_{}_SPEED".format(i), 200)) - self['MIN_SPINDLE_{}_SPEED'.format(i)] = int( - self.get_error_safe_setting("DISPLAY", "MIN_SPINDLE_{}_SPEED".format(i), 100)) - self['MAX_SPINDLE_{}_SPEED'.format(i)] = int( - self.get_error_safe_setting("DISPLAY", "MAX_SPINDLE_{}_SPEED".format(i), 2500)) - self['MAX_SPINDLE_{}_OVERRIDE'.format(i)] = float( - self.get_error_safe_setting("DISPLAY", "MAX_SPINDLE_{}_OVERRIDE".format(i), 1)) * 100 - self['MIN_SPINDLE_{}_OVERRIDE'.format(i)] = float( - self.get_error_safe_setting("DISPLAY", "MIN_SPINDLE_{}_OVERRIDE".format(i), 0.5)) * 100 + self['DEFAULT_SPINDLE_{}_SPEED'.format(i)] = \ + self.get_error_safe_int("DISPLAY", "DEFAULT_SPINDLE_{}_SPEED".format(i), 200) + self['MIN_SPINDLE_{}_SPEED'.format(i)] = \ + self.get_error_safe_int("DISPLAY", "MIN_SPINDLE_{}_SPEED".format(i), 100) + self['MAX_SPINDLE_{}_SPEED'.format(i)] = \ + self.get_error_safe_int("DISPLAY", "MAX_SPINDLE_{}_SPEED".format(i), 2500) + self['MAX_SPINDLE_{}_OVERRIDE'.format(i)] = \ + self.get_error_safe_float("DISPLAY", "MAX_SPINDLE_{}_OVERRIDE".format(i), 1) * 100 + self['MIN_SPINDLE_{}_OVERRIDE'.format(i)] = \ + self.get_error_safe_float("DISPLAY", "MIN_SPINDLE_{}_OVERRIDE".format(i), 0.5) * 100 +# FIXME:^^^^^ # check Legacy - self.DEFAULT_SPINDLE_SPEED = int(self.INI.find("DISPLAY", "DEFAULT_SPINDLE_SPEED") or -1) + self.DEFAULT_SPINDLE_SPEED = self.INI.getint("DISPLAY", "DEFAULT_SPINDLE_SPEED", fallback=-1) if self.DEFAULT_SPINDLE_SPEED < 0: self.DEFAULT_SPINDLE_SPEED = self.DEFAULT_SPINDLE_0_SPEED - self.MIN_SPINDLE_SPEED = int(self.INI.find("DISPLAY", "MIN_SPINDLE_SPEED") or -1) +# FIXME:vvvvv +# FIXME: MIN/MAX_SPINDLE_SPEED are real? + self.MIN_SPINDLE_SPEED = self.INI.getint("DISPLAY", "MIN_SPINDLE_SPEED", fallback=-1) if self.MIN_SPINDLE_SPEED < 0: self.MIN_SPINDLE_SPEED = self.MIN_SPINDLE_0_SPEED - self.MAX_SPINDLE_SPEED = int(self.INI.find("DISPLAY", "MAX_SPINDLE_SPEED") or -1) + self.MAX_SPINDLE_SPEED = self.INI.getint("DISPLAY", "MAX_SPINDLE_SPEED", fallback=-1) if self.MAX_SPINDLE_SPEED < 0: self.MAX_SPINDLE_SPEED = self.MAX_SPINDLE_0_SPEED - self.MAX_SPINDLE_OVERRIDE = float(self.INI.find("DISPLAY", "MAX_SPINDLE_OVERRIDE") or -1) * 100 +# FIXME:^^^^^ + self.MAX_SPINDLE_OVERRIDE = self.INI.getreal("DISPLAY", "MAX_SPINDLE_OVERRIDE", fallback=-1) * 100 if self.MAX_SPINDLE_OVERRIDE < 0: self.MAX_SPINDLE_OVERRIDE = self.MAX_SPINDLE_0_OVERRIDE - self.MIN_SPINDLE_OVERRIDE = float(self.INI.find("DISPLAY", "MIN_SPINDLE_OVERRIDE") or -1) * 100 + self.MIN_SPINDLE_OVERRIDE = self.INI.getreal("DISPLAY", "MIN_SPINDLE_OVERRIDE", fallback=-1) * 100 if self.MIN_SPINDLE_OVERRIDE < 0: self.MIN_SPINDLE_OVERRIDE = self.MIN_SPINDLE_0_OVERRIDE - self.MAX_FEED_OVERRIDE = float(self.get_error_safe_setting("DISPLAY", "MAX_FEED_OVERRIDE", 1.5)) * 100 + self.MAX_FEED_OVERRIDE = self.get_error_safe_float("DISPLAY", "MAX_FEED_OVERRIDE", 1.5) * 100 if self.INI.find("TRAJ", "MAX_LINEAR_VELOCITY") is None: if self.LINUXCNC_IS_RUNNING: LOG.critical('INI Parsing Error, No MAX_LINEAR_VELOCITY Entry in TRAJ') - self.MAX_TRAJ_VELOCITY = float(self.get_error_safe_setting("TRAJ", "MAX_LINEAR_VELOCITY", - self.get_error_safe_setting("AXIS_X", "MAX_VELOCITY", 5))) * 60 + self.MAX_TRAJ_VELOCITY = self.get_error_safe_float("TRAJ", "MAX_LINEAR_VELOCITY", + self.get_error_safe_float("AXIS_X", "MAX_VELOCITY", 5)) * 60 # user message dialog system self.USRMESS_BOLDTEXT = self.INI.findall("DISPLAY", "MESSAGE_BOLDTEXT") @@ -507,7 +504,7 @@ def update(self): ############## # AXIS panel style: - self.GLADEVCP = (self.INI.find("DISPLAY", "GLADEVCP")) or None + self.GLADEVCP = self.INI.find("DISPLAY", "GLADEVCP") # or None # tab style for qtvcp tab. style is used everywhere good_flag = True @@ -567,10 +564,10 @@ def update(self): self.MDI_COMMAND_LIST = [] self.MDI_COMMAND_LABEL_LIST = [] - # suppress error message is there is no section at all - if self.parser.has_section('MDI_COMMAND_LIST'): + # suppress error message if there is no section at all + if self.INI.hassection('MDI_COMMAND_LIST'): try: - for key in self.parser['MDI_COMMAND_LIST']: + for key,value in self.INI.getvariables('MDI_COMMAND_LIST'): # legacy way: list of repeat 'MDI_COMMAND=XXXX' # in this case order matters in the INI @@ -602,13 +599,12 @@ def update(self): self.MDI_COMMAND_LIST.append(None) self.MDI_COMMAND_LABEL_LIST.append(None) try: - temp = self.INI.find("MDI_COMMAND_LIST",key) name = (key.replace('MDI_COMMAND_','')) mdidatadict = {} - for num,k in enumerate(temp.split(',')): + for num,k in enumerate(value.split(',')): if num == 0: mdidatadict['cmd'] = k - if len(temp.split(',')) <2: + if len(value.split(',')) <2: mdidatadict['label'] = None else: mdidatadict['label'] = k @@ -623,12 +619,12 @@ def update(self): self.POSTGUI_HAL_COMMANDS = (self.INI.findall("HAL", "POSTGUI_HALCMD")) or None # Some systems need repeat disabled for keyboard jogging because repeat rate is uneven - self.DISABLE_REPEAT_KEYS_LIST = self.INI.find("DISPLAY", "DISABLE_REPEAT_KEYS") or None + self.DISABLE_REPEAT_KEYS_LIST = self.INI.getstring("DISPLAY", "DISABLE_REPEAT_KEYS") # or None # maximum number of errors shown in on screen display - self.MAX_DISPLAYED_ERRORS = int(self.INI.find("DISPLAY", "MAX_DISPLAYED_ERRORS") or 10) - self.TITLE = (self.INI.find("DISPLAY", "TITLE")) or "" - self.ICON = (self.INI.find("DISPLAY", "ICON")) or "" + self.MAX_DISPLAYED_ERRORS = self.INI.getint("DISPLAY", "MAX_DISPLAYED_ERRORS", fallback=10) + self.TITLE = self.INI.getstring("DISPLAY", "TITLE", fallback="") + self.ICON = self.INI.getstring("DISPLAY", "ICON", fallback="") # detect historical lathe config with dummy joint 1 if (self.MACHINE_IS_LATHE @@ -643,33 +639,42 @@ def update(self): ################### # return a found string or else None by default, anything else by option # since this is used in this file there are some workarounds for plasma machines + def _opt_default_warn(self, heading, detail, default, warning): + if ('SPINDLE' in detail and self.MACHINE_IS_QTPLASMAC) or \ + ('ANGULAR' in detail and not self.HAS_ANGULAR_JOINT): + return default + elif warning: + LOG.warning('INI Parsing Error, No {} Entry in {}, Using: {}'.format(detail, heading, default)) + return default + + def get_error_safe_int(self, heading, detail, default=None, warning = True): + result = self.INI.getint(heading, detail) + if result is not None: + return result + else: + return _opt_default_warn(heading, detail, default, warning) + + def get_error_safe_float(self, heading, detail, default=None, warning = True): + result = self.INI.getreal(heading, detail) + if result is not None: + return result + else: + return _opt_default_warn(heading, detail, default, warning) + def get_error_safe_setting(self, heading, detail, default=None, warning = True): result = self.INI.find(heading, detail) - if result: + if result is not None: return result else: - if ('SPINDLE' in detail and self.MACHINE_IS_QTPLASMAC) or \ - ('ANGULAR' in detail and not self.HAS_ANGULAR_JOINT): - return default - elif warning: - LOG.warning('INI Parsing Error, No {} Entry in {}, Using: {}'.format(detail, heading, default)) - return default + return _opt_default_warn(heading, detail, default, warning) # return a found float or else None by default, anything else by option def get_safe_float(self, heading, detail, default=None): - try: - result = float(self.INI.find(heading, detail)) - return result - except: - return default + return self.INI.getreal(heading, detail, fallback=default) # return a found integer or else None by default, anything else by option def get_safe_int(self, heading, detail, default=None): - try: - result = int(self.INI.find(heading, detail)) - return result - except: - return default + return self.INI.getint(heading, detail, fallback=default) def convert_machine_to_metric(self, data): if self.MACHINE_IS_METRIC: diff --git a/lib/python/gladevcp/hal_actions.py b/lib/python/gladevcp/hal_actions.py index 76e3be33c0c..2adacc30110 100644 --- a/lib/python/gladevcp/hal_actions.py +++ b/lib/python/gladevcp/hal_actions.py @@ -66,7 +66,7 @@ def _hal_init(self): # if 'NO_FORCE_HOMING' is true, MDI commands are allowed before homing. inifile = os.environ.get('INI_FILE_NAME', '/dev/null') ini = linuxcnc.ini(inifile) - self.no_f_home = int(ini.find("TRAJ", "NO_FORCE_HOMING") or 0) + self.no_f_home = ini.getbool("TRAJ", "NO_FORCE_HOMING", fallback=False) def machine_on(self): self.stat.poll() diff --git a/lib/python/gladevcp/macro_widget.py b/lib/python/gladevcp/macro_widget.py index d9f83134a7a..044f76395b3 100644 --- a/lib/python/gladevcp/macro_widget.py +++ b/lib/python/gladevcp/macro_widget.py @@ -53,9 +53,9 @@ def __init__(self, *a, **kw): try: inifile = os.environ.get('INI_FILE_NAME', '/dev/null') self.ini = linuxcnc.ini(inifile) - no_home_required = int(self.ini.find("TRAJ", "NO_FORCE_HOMING") or 0) + no_home_required = self.ini.getbool("TRAJ", "NO_FORCE_HOMING", fallback=False) macros = self.inifile.findall("MACROS", "MACRO") - sub_path = self.inifile.find("RS274NGC", "SUBROUTINE_PATH")or '~/linuxcnc/nc_files/macros' + sub_path = self.inifile.getstring("RS274NGC", "SUBROUTINE_PATH", fallback='~/linuxcnc/nc_files/macros') except: self.ini = None no_home_required = 1 diff --git a/lib/python/gladevcp/overridewidget.py b/lib/python/gladevcp/overridewidget.py index c763aaea87c..ab8e34dd7fe 100644 --- a/lib/python/gladevcp/overridewidget.py +++ b/lib/python/gladevcp/overridewidget.py @@ -57,7 +57,7 @@ def set_type(self, data=0): self.override_type = data self.inifile = self.emc.ini(INIPATH) if self.override_type == 0: - MAXFEED = float(self.inifile.find("DISPLAY","MAX_FEED_OVERRIDE") or 2.0) + MAXFEED = self.inifile.getreal("DISPLAY","MAX_FEED_OVERRIDE", fallback=2.0) #print 'feed',MAXFEED adjustment.set_upper(MAXFEED*100) adjustment.set_lower(0) @@ -66,14 +66,14 @@ def set_type(self, data=0): adjustment.set_upper(100) adjustment.set_lower(0) elif self.override_type == 2: - MINSPINDLE = float(self.inifile.find("DISPLAY","MIN_SPINDLE_OVERRIDE") or .5) + MINSPINDLE = self.inifile.getreal("DISPLAY","MIN_SPINDLE_OVERRIDE", fallback=0.5) #print 'mins',MINSPINDLE - MAXSPINDLE = float(self.inifile.find("DISPLAY","MAX_SPINDLE_OVERRIDE") or 1.5) + MAXSPINDLE = self.inifile.getreal("DISPLAY","MAX_SPINDLE_OVERRIDE", fallback=1.5) #print 'maxs',MAXSPINDLE adjustment.set_upper(MAXSPINDLE*100) adjustment.set_lower(MINSPINDLE*100) elif self.override_type == 3: - MAXVEL = float(self.inifile.find("TRAJ","MAX_LINEAR_VELOCITY") or 100) + MAXVEL = self.inifile.getreal("TRAJ","MAX_LINEAR_VELOCITY", fallback=100) #print 'maxv',MAXVEL,MAXVEL/100.0 self.max_vel_convert = MAXVEL/100.0 adjustment.set_upper(100) diff --git a/lib/python/gladevcp/tooledit_widget.py b/lib/python/gladevcp/tooledit_widget.py index b3331358352..c45ba72f080 100644 --- a/lib/python/gladevcp/tooledit_widget.py +++ b/lib/python/gladevcp/tooledit_widget.py @@ -165,17 +165,9 @@ def compare(model, row1, row2, user_data=None): if toolfile: self.reload(None) # check the INI file if display-type: LATHE is set - try: - self.inifile = linuxcnc.ini(INIPATH) - test = self.inifile.find("DISPLAY", "LATHE") - if test == '1' or test == 'True': - self.lathe_display_type = True - self.set_lathe_display(True) - else: - self.lathe_display_type = False - self.set_lathe_display(False) - except: - pass + self.inifile = linuxcnc.ini(INIPATH) + self.lathe_display_type = self.inifile.getbool("DISPLAY", "LATHE", fallback=False) + self.set_lathe_display(self.lathe_display_type) # check linuxcnc status every second GLib.timeout_add(1000, self.periodic_check) diff --git a/lib/python/qtvcp/qt_istat.py b/lib/python/qtvcp/qt_istat.py index c53c777a02d..57cb26c6906 100644 --- a/lib/python/qtvcp/qt_istat.py +++ b/lib/python/qtvcp/qt_istat.py @@ -1,10 +1,6 @@ import os import linuxcnc import collections -import configparser - -PARSER = configparser.RawConfigParser -PARSER.optionxform = str # Set up logging from . import logger @@ -88,38 +84,31 @@ def __init__(self, ini=None): self.update() def update(self): - - # use configParser so we can iter thru header - self.parser = PARSER(strict=False) - try: - self.parser.read(filenames=self.INIPATH) - except: - pass - - ct = float(self.INI.find('DISPLAY', 'CYCLE_TIME') or 100) # possibly in seconds or ms + ct = self.INI.getreal('DISPLAY', 'CYCLE_TIME', fallback=100) # possibly in seconds or ms if ct < 1: self.CYCLE_TIME = int(ct * 1000) else: self.CYCLE_TIME = int(ct) - self.GRAPHICS_CYCLE_TIME =int(self.INI.find('DISPLAY', 'GRAPHICS_CYCLE_TIME') or 100) # in seconds - self.HALPIN_CYCLE_TIME = int(self.INI.find('DISPLAY', 'HALPIN_CYCLE_TIME') or 100) # in seconds - self.MDI_HISTORY_PATH = self.INI.find('DISPLAY', 'MDI_HISTORY_FILE') or '~/.axis_mdi_history' - self.QTVCP_LOG_HISTORY_PATH = self.INI.find('DISPLAY', 'LOG_FILE') or '~/qtvcp.log' - self.MACHINE_LOG_HISTORY_PATH = self.INI.find('DISPLAY', 'MACHINE_LOG_PATH') or '~/.machine_log_history' - self.PREFERENCE_PATH = self.INI.find("DISPLAY", "PREFERENCE_FILE_PATH") or None + self.GRAPHICS_CYCLE_TIME = self.INI.getint('DISPLAY', 'GRAPHICS_CYCLE_TIME', fallback=100) # in seconds + self.HALPIN_CYCLE_TIME = self.INI.getint('DISPLAY', 'HALPIN_CYCLE_TIME', fallback=100) # in seconds + self.MDI_HISTORY_PATH = self.INI.getstring('DISPLAY', 'MDI_HISTORY_FILE', fallback='~/.axis_mdi_history') + self.QTVCP_LOG_HISTORY_PATH = self.INI.getstring('DISPLAY', 'LOG_FILE', fallback='~/qtvcp.log') + self.MACHINE_LOG_HISTORY_PATH = self.INI.getstring('DISPLAY', 'MACHINE_LOG_PATH', fallback='~/.machine_log_history') + self.PREFERENCE_PATH = self.INI.getstring("DISPLAY", "PREFERENCE_FILE_PATH") # or None self.PROGRAM_PREFIX = self.get_error_safe_setting("DISPLAY", "PROGRAM_PREFIX", '~/linuxcnc/nc_files') + if not os.path.exists(os.path.expanduser(self.PROGRAM_PREFIX)): log.warning('Path not valid in INI File [DISPLAY] PROGRAM_PREFIX section') - temp = self.INI.find("DISPLAY", "USER_COMMAND_FILE") + temp = self.INI.getstring("DISPLAY", "USER_COMMAND_FILE") if not temp is None: self.USER_COMMAND_FILE = os.path.expanduser(temp) else: self.USER_COMMAND_FILE = None - self.STARTUP_CODES = (self.INI.find('RS274NGC', 'RS274NGC_STARTUP_CODE') ) or None + self.STARTUP_CODES = self.INI.getstring('RS274NGC', 'RS274NGC_STARTUP_CODE') # or None - self.SUB_PATH = (self.INI.find("RS274NGC", "SUBROUTINE_PATH")) or None + self.SUB_PATH = self.INI.getstring("RS274NGC", "SUBROUTINE_PATH") # or None if self.SUB_PATH is not None: for mpath in (self.SUB_PATH.split(':')): self.SUB_PATH_LIST.append(mpath) @@ -130,21 +119,21 @@ def update(self): else: self.MACRO_PATH = None - self.USER_M_PATH = (self.INI.find("RS274NGC", "USER_M_PATH")) or None + self.USER_M_PATH = self.INI.getstring("RS274NGC", "USER_M_PATH") # or None if self.USER_M_PATH is not None: for mpath in (self.USER_M_PATH.split(':')): self.USER_M_PATH_LIST.append(mpath) self.INI_MACROS = self.INI.findall("DISPLAY", "MACRO") - self.NGC_SUB_PATH = (self.INI.find("DISPLAY","NGCGUI_SUBFILE_PATH")) or None + self.NGC_SUB_PATH = self.INI.getstring("DISPLAY","NGCGUI_SUBFILE_PATH") # or None if not self.NGC_SUB_PATH is None: self.NGC_SUB_PATH = os.path.expanduser(self.NGC_SUB_PATH) - self.NGC_SUB = (self.INI.findall("DISPLAY", "NGCGUI_SUBFILE")) or None + self.NGC_SUB = self.INI.findall("DISPLAY", "NGCGUI_SUBFILE") or None - self.MACHINE_IS_LATHE = bool(self.INI.find("DISPLAY", "LATHE")) + self.MACHINE_IS_LATHE = self.INI.getbool("DISPLAY", "LATHE", fallback=False) try: - self.MACHINE_IS_QTPLASMAC = 'qtplasmac' in self.INI.find("DISPLAY", "DISPLAY") + self.MACHINE_IS_QTPLASMAC = 'qtplasmac' in self.INI.getstring("DISPLAY", "DISPLAY") except: self.MACHINE_IS_QTPLASMAC = False extensions = self.INI.findall("FILTER", "PROGRAM_EXTENSION") @@ -152,19 +141,19 @@ def update(self): self.PROGRAM_FILTERS_EXTENSIONS = self.get_filters_extensions() self.VALID_PROGRAM_EXTENSIONS = self.get_all_valid_extensions() - self.PARAMETER_FILE = (self.INI.find("RS274NGC", "PARAMETER_FILE")) or None + self.PARAMETER_FILE = self.INI.getstring("RS274NGC", "PARAMETER_FILE") # or None if self.PARAMETER_FILE is None and self.LINUXCNC_IS_RUNNING: log.critical('Missing PARAMETER_FILE setting in RS274NGC section') try: # check the INI file if UNITS are set to mm" # first check the global settings - units = self.INI.find("TRAJ", "LINEAR_UNITS") + units = self.INI.getstring("TRAJ", "LINEAR_UNITS") if units is None: if self.LINUXCNC_IS_RUNNING: log.critical('Missing LINEAR_UNITS in TRAJ, guessing units for machine from JOINT 0') # else then guess; The joint 0 is usually X axis - units = self.INI.find("JOINT_0", "UNITS") + units = self.INI.getstring("JOINT_0", "UNITS") if units is None: if self.LINUXCNC_IS_RUNNING: log.critical('Missing UNITS in JOINT_0, assuming metric based machine') @@ -187,9 +176,9 @@ def update(self): self.MACHINE_UNIT_CONVERSION_10 = [25.4] * 3 + [1] * 3 + [25.4] * 3 + [1] log.debug('Machine is IMPERIAL based. unit Conversion constant={}'.format(self.MACHINE_UNIT_CONVERSION)) - axes = self.INI.find("TRAJ", "COORDINATES") + axes = self.INI.getstring("TRAJ", "COORDINATES") try: - self.trajcoordinates = self.INI.find("TRAJ", "COORDINATES").lower().replace(" ","") + self.trajcoordinates = self.INI.getstring("TRAJ", "COORDINATES").lower().replace(" ","") except: self.trajcoordinates ='xyz' @@ -229,11 +218,11 @@ def update(self): self.AVAILABLE_JOINTS.append(num) # AXIS sanity check - av = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_VELOCITY') or None - aa = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_ACCELERATION') or None + av = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_VELOCITY') # or None + aa = self.INI.find('AXIS_%s' % letter.upper(), 'MAX_ACCELERATION') # or None if av is None or aa is None: # some lathe configs have dummy Y axis for axis rotation G code - if letter == "Y" and self.MACHINE_IS_LATHE: + if letter.upper() == "Y" and self.MACHINE_IS_LATHE: pass else: log.critical( @@ -249,7 +238,7 @@ def update(self): self.GET_AXIS_INDEX_FROM_JOINT_NUM[int(i)] = int(axisnum) self.GET_JOINT_NUM_FROM_AXIS_INDEX[int(axisnum)] = int(i) - self.NO_HOME_REQUIRED = int(self.INI.find("TRAJ", "NO_FORCE_HOMING") or 0) + self.NO_HOME_REQUIRED = self.INI.getbool("TRAJ", "NO_FORCE_HOMING", fallback=False) # home all check self.HOME_ALL_FLAG = 1 @@ -257,11 +246,11 @@ def update(self): jointcount = len(self.AVAILABLE_JOINTS) self.JOINT_SEQUENCE_LIST = {} for j in range(jointcount): - seq = self.INI.find("JOINT_" + str(j), "HOME_SEQUENCE") + seq = self.INI.getint("JOINT_" + str(j), "HOME_SEQUENCE") if seq is None: seq = 0 self.HOME_ALL_FLAG = 0 - self.JOINT_SEQUENCE_LIST[j] = int(seq) + self.JOINT_SEQUENCE_LIST[j] = seq # joint sequence/type self.JOINT_TYPE = [None] * jointcount self.JOINT_TYPE_INT = [None] * jointcount @@ -275,7 +264,7 @@ def update(self): else: self.JOINT_TYPE_INT[j] = 2 self.HAS_ANGULAR_JOINT = True - self.JOINT_SEQUENCE[j] = int(self.INI.find(section, "HOME_SEQUENCE") or 0) + self.JOINT_SEQUENCE[j] = self.INI.getint(section, "HOME_SEQUENCE", fallback=0) # jog synchronized sequence # gives a list of joints combined to make an axis @@ -366,12 +355,12 @@ def update(self): self.TRAJ_COORDINATES = temp.lower().replace(" ", "") else: self.TRAJ_COORDINATES = None - self.JOINT_COUNT = int(self.INI.find("KINS", "JOINTS") or 0) + self.JOINT_COUNT = self.INI.getint("KINS", "JOINTS", fallback=0) # check for weird kinematics like robots self.IS_TRIVIAL_MACHINE = bool('trivkins' in self.get_error_safe_setting("KINS", "KINEMATICS",'trivial')) - kinsmodule = self.INI.find("KINS", "KINEMATICS") or 'trivkins' + kinsmodule = self.INI.getstring("KINS", "KINEMATICS", fallback='trivkins') if kinsmodule.split()[0] == "trivkins": self.trivkinscoords = "XYZABCUVW" for item in kinsmodule.split(): @@ -379,57 +368,63 @@ def update(self): self.trivkinscoords = item.split("=")[1].upper() safe = 25 if self.MACHINE_IS_METRIC else 1 - self.DEFAULT_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "DEFAULT_LINEAR_VELOCITY", safe)) * 60 - self.MIN_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MIN_LINEAR_VELOCITY", 0)) * 60 + self.DEFAULT_LINEAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "DEFAULT_LINEAR_VELOCITY", safe) * 60 + self.MIN_LINEAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MIN_LINEAR_VELOCITY", 0) * 60 safe = 125 if self.MACHINE_IS_METRIC else 5 - self.MAX_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MAX_LINEAR_VELOCITY", safe)) * 60 + self.MAX_LINEAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MAX_LINEAR_VELOCITY", safe) * 60 log.debug('DEFAULT_LINEAR_VELOCITY = {}'.format(self.DEFAULT_LINEAR_JOG_VEL)) log.debug('MIN_LINEAR_VELOCITY = {}'.format(self.MIN_LINEAR_JOG_VEL)) log.debug('MAX_LINEAR_VELOCITY = {}'.format(self.MAX_LINEAR_JOG_VEL)) - self.DEFAULT_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "DEFAULT_ANGULAR_VELOCITY", 6)) * 60 - self.MIN_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MIN_ANGULAR_VELOCITY", 0)) * 60 - self.MAX_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY", "MAX_ANGULAR_VELOCITY", 60)) * 60 + self.DEFAULT_ANGULAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "DEFAULT_ANGULAR_VELOCITY", 6) * 60 + self.MIN_ANGULAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MIN_ANGULAR_VELOCITY", 0) * 60 + self.MAX_ANGULAR_JOG_VEL = self.get_error_safe_float("DISPLAY", "MAX_ANGULAR_VELOCITY", 60) * 60 log.debug('DEFAULT_ANGULAR_VELOCITY = {}'.format(self.DEFAULT_ANGULAR_JOG_VEL)) log.debug('MIN_ANGULAR_VELOCITY = {}'.format(self.MIN_ANGULAR_JOG_VEL)) log.debug('MAX_ANGULAR_VELOCITY = {}'.format(self.MAX_ANGULAR_JOG_VEL)) - self.AVAILABLE_SPINDLES = int(self.INI.find("TRAJ", "SPINDLES") or 1) - self.SPINDLE_INCREMENT = int(self.INI.find("DISPLAY", "SPINDLE_INCREMENT") or 100) + self.AVAILABLE_SPINDLES = self.INI.getint("TRAJ", "SPINDLES", fallback=1) + self.SPINDLE_INCREMENT = self.INI.getint("DISPLAY", "SPINDLE_INCREMENT", fallback=100) +# FIXME:vvvvv +# FIXME: Aren't SPEED values real values? Velocity values are. for i in range(0, self.AVAILABLE_SPINDLES): - self['DEFAULT_SPINDLE_{}_SPEED'.format(i)] = int( - self.get_error_safe_setting("DISPLAY", "DEFAULT_SPINDLE_{}_SPEED".format(i), 200)) - self['MIN_SPINDLE_{}_SPEED'.format(i)] = int( - self.get_error_safe_setting("DISPLAY", "MIN_SPINDLE_{}_SPEED".format(i), 100)) - self['MAX_SPINDLE_{}_SPEED'.format(i)] = int( - self.get_error_safe_setting("DISPLAY", "MAX_SPINDLE_{}_SPEED".format(i), 2500)) - self['MAX_SPINDLE_{}_OVERRIDE'.format(i)] = float( - self.get_error_safe_setting("DISPLAY", "MAX_SPINDLE_{}_OVERRIDE".format(i), 1)) * 100 - self['MIN_SPINDLE_{}_OVERRIDE'.format(i)] = float( - self.get_error_safe_setting("DISPLAY", "MIN_SPINDLE_{}_OVERRIDE".format(i), 0.5)) * 100 + self['DEFAULT_SPINDLE_{}_SPEED'.format(i)] = \ + self.get_error_safe_int("DISPLAY", "DEFAULT_SPINDLE_{}_SPEED".format(i), 200) + self['MIN_SPINDLE_{}_SPEED'.format(i)] = \ + self.get_error_safe_int("DISPLAY", "MIN_SPINDLE_{}_SPEED".format(i), 100) + self['MAX_SPINDLE_{}_SPEED'.format(i)] = \ + self.get_error_safe_int("DISPLAY", "MAX_SPINDLE_{}_SPEED".format(i), 2500) + self['MAX_SPINDLE_{}_OVERRIDE'.format(i)] = \ + self.get_error_safe_float("DISPLAY", "MAX_SPINDLE_{}_OVERRIDE".format(i), 1) * 100 + self['MIN_SPINDLE_{}_OVERRIDE'.format(i)] = \ + self.get_error_safe_float("DISPLAY", "MIN_SPINDLE_{}_OVERRIDE".format(i), 0.5) * 100 +# FIXME:^^^^^ # check Legacy - self.DEFAULT_SPINDLE_SPEED = int(self.INI.find("DISPLAY", "DEFAULT_SPINDLE_SPEED") or -1) + self.DEFAULT_SPINDLE_SPEED = self.INI.getint("DISPLAY", "DEFAULT_SPINDLE_SPEED", fallback=-1) if self.DEFAULT_SPINDLE_SPEED < 0: self.DEFAULT_SPINDLE_SPEED = self.DEFAULT_SPINDLE_0_SPEED - self.MIN_SPINDLE_SPEED = int(self.INI.find("DISPLAY", "MIN_SPINDLE_SPEED") or -1) +# FIXME:vvvvv +# FIXME: MIN/MAX_SPINDLE_SPEED are real? + self.MIN_SPINDLE_SPEED = self.INI.getint("DISPLAY", "MIN_SPINDLE_SPEED", fallback=-1) if self.MIN_SPINDLE_SPEED < 0: self.MIN_SPINDLE_SPEED = self.MIN_SPINDLE_0_SPEED - self.MAX_SPINDLE_SPEED = int(self.INI.find("DISPLAY", "MAX_SPINDLE_SPEED") or -1) + self.MAX_SPINDLE_SPEED = self.INI.getint("DISPLAY", "MAX_SPINDLE_SPEED", fallback=-1) if self.MAX_SPINDLE_SPEED < 0: self.MAX_SPINDLE_SPEED = self.MAX_SPINDLE_0_SPEED - self.MAX_SPINDLE_OVERRIDE = float(self.INI.find("DISPLAY", "MAX_SPINDLE_OVERRIDE") or -1) * 100 +# FIXME:^^^^^ + self.MAX_SPINDLE_OVERRIDE = self.INI.getreal("DISPLAY", "MAX_SPINDLE_OVERRIDE", fallback=-1) * 100 if self.MAX_SPINDLE_OVERRIDE < 0: self.MAX_SPINDLE_OVERRIDE = self.MAX_SPINDLE_0_OVERRIDE - self.MIN_SPINDLE_OVERRIDE = float(self.INI.find("DISPLAY", "MIN_SPINDLE_OVERRIDE") or -1) * 100 + self.MIN_SPINDLE_OVERRIDE = self.INI.getreal("DISPLAY", "MIN_SPINDLE_OVERRIDE", fallback=-1) * 100 if self.MIN_SPINDLE_OVERRIDE < 0: self.MIN_SPINDLE_OVERRIDE = self.MIN_SPINDLE_0_OVERRIDE - self.MAX_FEED_OVERRIDE = float(self.get_error_safe_setting("DISPLAY", "MAX_FEED_OVERRIDE", 1.5)) * 100 + self.MAX_FEED_OVERRIDE = self.get_error_safe_float("DISPLAY", "MAX_FEED_OVERRIDE", 1.5) * 100 if self.INI.find("TRAJ", "MAX_LINEAR_VELOCITY") is None: if self.LINUXCNC_IS_RUNNING: log.critical('INI Parsing Error, No MAX_LINEAR_VELOCITY Entry in TRAJ') - self.MAX_TRAJ_VELOCITY = float(self.get_error_safe_setting("TRAJ", "MAX_LINEAR_VELOCITY", - self.get_error_safe_setting("AXIS_X", "MAX_VELOCITY", 5))) * 60 + self.MAX_TRAJ_VELOCITY = self.get_error_safe_float("TRAJ", "MAX_LINEAR_VELOCITY", + self.get_error_safe_float("AXIS_X", "MAX_VELOCITY", 5)) * 60 # user message dialog system self.USRMESS_BOLDTEXT = self.INI.findall("DISPLAY", "MESSAGE_BOLDTEXT") @@ -537,7 +532,7 @@ def update(self): ############## # AXIS panel style: - self.GLADEVCP = (self.INI.find("DISPLAY", "GLADEVCP")) or None + self.GLADEVCP = self.INI.find("DISPLAY", "GLADEVCP") # or None # tab style for qtvcp tab. style is used everywhere good_flag = True @@ -594,10 +589,10 @@ def update(self): # here we separate them to two lists (legacy) and one dict # action_button takes it from there. self.MDI_COMMAND_DICT={} - # suppress error message is there is no section at all - if self.parser.has_section('MDI_COMMAND_LIST'): + # suppress error message if there is no section at all + if self.INI.hassection('MDI_COMMAND_LIST'): try: - for key in self.parser['MDI_COMMAND_LIST']: + for key,value in self.INI.getvariables('MDI_COMMAND_LIST'): # legacy way: list of repeat 'MDI_COMMAND=XXXX' # in this case order matters in the INI @@ -623,13 +618,12 @@ def update(self): # order of commands doesn't matter in the INI else: try: - temp = self.INI.find("MDI_COMMAND_LIST",key) name = (key.replace('MDI_COMMAND_','')) mdidatadict = {} - for num,k in enumerate(temp.split(',')): + for num,k in enumerate(value.split(',')): if num == 0: mdidatadict['cmd'] = k - if len(temp.split(',')) <2: + if len(value.split(',')) <2: mdidatadict['label'] = None else: mdidatadict['label'] = k @@ -644,12 +638,12 @@ def update(self): self.POSTGUI_HAL_COMMANDS = (self.INI.findall("HAL", "POSTGUI_HALCMD")) or None # Some systems need repeat disabled for keyboard jogging because repeat rate is uneven - self.DISABLE_REPEAT_KEYS_LIST = self.INI.find("DISPLAY", "DISABLE_REPEAT_KEYS") or None + self.DISABLE_REPEAT_KEYS_LIST = self.INI.getstring("DISPLAY", "DISABLE_REPEAT_KEYS") # or None # maximum number of errors shown in on screen display - self.MAX_DISPLAYED_ERRORS = int(self.INI.find("DISPLAY", "MAX_DISPLAYED_ERRORS") or 10) - self.TITLE = (self.INI.find("DISPLAY", "TITLE")) or "" - self.ICON = (self.INI.find("DISPLAY", "ICON")) or "" + self.MAX_DISPLAYED_ERRORS = self.INI.getint("DISPLAY", "MAX_DISPLAYED_ERRORS", fallback=10) + self.TITLE = self.INI.getstring("DISPLAY", "TITLE", fallback="") + self.ICON = self.INI.getstring("DISPLAY", "ICON", fallback="") # detect historical lathe config with dummy joint 1 if (self.MACHINE_IS_LATHE @@ -664,33 +658,42 @@ def update(self): ################### # return a found string or else None by default, anything else by option # since this is used in this file there are some workarounds for plasma machines + def _opt_default_warn(self, heading, detail, default, warning): + if ('SPINDLE' in detail and self.MACHINE_IS_QTPLASMAC) or \ + ('ANGULAR' in detail and not self.HAS_ANGULAR_JOINT): + return default + elif warning: + log.warning('INI Parsing Error, No {} Entry in {}, Using: {}'.format(detail, heading, default)) + return default + + def get_error_safe_int(self, heading, detail, default=None, warning = True): + result = self.INI.getint(heading, detail) + if result is not None: + return result + else: + return _opt_default_warn(heading, detail, default, warning) + + def get_error_safe_float(self, heading, detail, default=None, warning = True): + result = self.INI.getreal(heading, detail) + if result is not None: + return result + else: + return _opt_default_warn(heading, detail, default, warning) + def get_error_safe_setting(self, heading, detail, default=None, warning = True): result = self.INI.find(heading, detail) - if result: + if result is not None: return result else: - if ('SPINDLE' in detail and self.MACHINE_IS_QTPLASMAC) or \ - ('ANGULAR' in detail and not self.HAS_ANGULAR_JOINT): - return default - elif warning: - log.warning('INI Parsing Error, No {} Entry in {}, Using: {}'.format(detail, heading, default)) - return default + return _opt_default_warn(heading, detail, default, warning) # return a found float or else None by default, anything else by option def get_safe_float(self, heading, detail, default=None): - try: - result = float(self.INI.find(heading, detail)) - return result - except: - return default + return self.INI.getreal(heading, detail, fallback=default) # return a found integer or else None by default, anything else by option def get_safe_int(self, heading, detail, default=None): - try: - result = int(self.INI.find(heading, detail)) - return result - except: - return default + return self.INI.getint(heading, detail, fallback=default) def convert_machine_to_metric(self, data): if self.MACHINE_IS_METRIC: diff --git a/lib/python/qtvcp/widgets/container_widgets.py b/lib/python/qtvcp/widgets/container_widgets.py index 322dffe5410..5aa28acdfe8 100644 --- a/lib/python/qtvcp/widgets/container_widgets.py +++ b/lib/python/qtvcp/widgets/container_widgets.py @@ -38,7 +38,7 @@ def __init__(self, parent=None): # if 'NO_FORCE_HOMING' is true, MDI commands are allowed before homing. self.inifile = os.environ.get('INI_FILE_NAME', '/dev/null') self.ini = linuxcnc.ini(self.inifile) - self.no_home_required = int(self.ini.find("TRAJ", "NO_FORCE_HOMING") or 0) + self.no_home_required = self.ini.getbool("TRAJ", "NO_FORCE_HOMING", fallback=False) self.is_on = False self.is_homed = False self.is_idle = False diff --git a/lib/python/qtvcp/widgets/probe_routines.py b/lib/python/qtvcp/widgets/probe_routines.py index e48c25de9ad..ec8c6bfd1c3 100644 --- a/lib/python/qtvcp/widgets/probe_routines.py +++ b/lib/python/qtvcp/widgets/probe_routines.py @@ -242,8 +242,8 @@ def probe_tool_with_toolsetter(self): # if so see if there is enough room in X axis limits if self.data_tool_diameter > self.data_ts_diam: # if close to edge of machine X, offset in the opposite direction - xplimit = float(INFO.INI.find('AXIS_X','MAX_LIMIT')) - xmlimit = float(INFO.INI.find('AXIS_X','MIN_LIMIT')) + xplimit = INFO.INI.getreal('AXIS_X','MAX_LIMIT') + xmlimit = INFO.INI.getreal('AXIS_X','MIN_LIMIT') if not (self.data_tool_diameter/2+self.data_ts_x) > xplimit: Xoffset = self.data_tool_diameter/2 elif not (self.data_ts_x -(self.data_tool_diameter/2)) < xmlimit: @@ -335,16 +335,16 @@ def probe_tool_z_diam(self): return 'failed: {}'.format(rtn) # confirm there is enough axis room to offset for diameters of tool and toolsetter - xplimit = float(INFO.INI.find('AXIS_X','MAX_LIMIT')) - xmlimit = float(INFO.INI.find('AXIS_X','MIN_LIMIT')) + xplimit = INFO.INI.getreal('AXIS_X','MAX_LIMIT') + xmlimit = INFO.INI.getreal('AXIS_X','MIN_LIMIT') offset = (self.data_tool_diameter+self.data_ts_diam)*.5 if (offset+self.data_ts_x) > xplimit: return 'cannot offset enough in + X for tool radius + toolsetter radius' elif (self.data_ts_x -(offset)) < xmlimit: return 'cannot offset enough in - X for tool radius + toolsetter radius' - yplimit = float(INFO.INI.find('AXIS_Y','MAX_LIMIT')) - ymlimit = float(INFO.INI.find('AXIS_Y','MIN_LIMIT')) + yplimit = INFO.INI.getreal('AXIS_Y','MAX_LIMIT') + ymlimit = INFO.INI.getreal('AXIS_Y','MIN_LIMIT') if (offset+self.data_ts_y) > yplimit: return 'cannot offset enough in + Y for tool radius offset + toolsetter radius' elif (self.data_ts_y -(offset)) < ymlimit: diff --git a/lib/python/qtvcp/widgets/versa_probe.py b/lib/python/qtvcp/widgets/versa_probe.py index a0c52e1804f..ef1706d5b27 100644 --- a/lib/python/qtvcp/widgets/versa_probe.py +++ b/lib/python/qtvcp/widgets/versa_probe.py @@ -506,7 +506,7 @@ def update_probe_height_pin(self, text): def update_block_height_pin(self, text): value = float(text) #if value == self.pin_bheight.get(): return - origin = float(INFO.INI.find("AXIS_Z", "MIN_LIMIT")) + value + origin = INFO.INI.getreal("AXIS_Z", "MIN_LIMIT") + value ACTION.CALL_MDI_WAIT( "G10 L2 P0 Z%s" % origin ) try: self.pin_bheight.set(value) diff --git a/lib/python/rs274/glcanon.py b/lib/python/rs274/glcanon.py index b2e43be96fe..1a8bf3db70e 100644 --- a/lib/python/rs274/glcanon.py +++ b/lib/python/rs274/glcanon.py @@ -535,7 +535,7 @@ def __init__(self, s=None, lp=None, g=None): if os.environ["INI_FILE_NAME"]: self.inifile = linuxcnc.ini(os.environ["INI_FILE_NAME"]) - if self.inifile.find("DISPLAY", "DRO_FORMAT_IN"): + if self.inifile.hasvariable("DISPLAY", "DRO_FORMAT_IN"): temp = self.inifile.find("DISPLAY", "DRO_FORMAT_IN") try: test = temp % 1.234 @@ -544,7 +544,7 @@ def __init__(self, s=None, lp=None, g=None): else: self.dro_in = temp - if self.inifile.find("DISPLAY", "DRO_FORMAT_MM"): + if self.inifile.hasvariable("DISPLAY", "DRO_FORMAT_MM"): temp = self.inifile.find("DISPLAY", "DRO_FORMAT_MM") try: test = temp % 1.234 @@ -554,19 +554,19 @@ def __init__(self, s=None, lp=None, g=None): self.dro_mm = temp self.dro_in = temp - self.foam_w_height = float(self.inifile.find("DISPLAY", "FOAM_W") or 1.5) - self.foam_z_height = float(self.inifile.find("DISPLAY", "FOAM_Z") or 0) + self.foam_w_height = self.inifile.getreal("DISPLAY", "FOAM_W", fallback=1.5) + self.foam_z_height = self.inifile.getreal("DISPLAY", "FOAM_Z", fallback=0) - size = (self.inifile.find("DISPLAY", "CONE_BASESIZE") or None) + size = self.inifile.getreal("DISPLAY", "CONE_BASESIZE") if size is not None: - self.set_cone_basesize(float(size)) + self.set_cone_basesize(size) # set maximum file size before showing boundary box instead - temp = self.inifile.find("DISPLAY", "GRAPHICAL_MAX_FILE_SIZE") + temp = self.inifile.getint("DISPLAY", "GRAPHICAL_MAX_FILE_SIZE") if not temp is None: - self.max_file_size = int(temp) * 1024 * 1024 + self.max_file_size = temp * 1024 * 1024 - self.disable_cone_scaling = bool(self.inifile.find("DISPLAY", "DISABLE_CONE_SCALING")) + self.disable_cone_scaling = self.inifile.getbool("DISPLAY", "DISABLE_CONE_SCALING", fallback=False) except: # Probably started in an editor so no INI diff --git a/scripts/linuxcnc.in b/scripts/linuxcnc.in index 7b1150204a1..cb6d0da9971 100644 --- a/scripts/linuxcnc.in +++ b/scripts/linuxcnc.in @@ -356,57 +356,6 @@ if [ -z "$INIFILE" ] ; then exit 0 fi -function handle_includes () { - hdr="# handle_includes():" - inifile="$1" - cd "$(dirname "$inifile")" || { echo "E: Could not change directory to '$(dirname "$inifile")'"; exit 1; } ;# for the function() subprocess only - $GREP "^#INCLUDE" "$inifile" >/dev/null - status=$? - if [ $status -ne 0 ] ; then - echo "$inifile" ;# just use the input - return 0 ;# ok - fi - outfile="$(dirname "$inifile")/$(basename "$inifile").expanded" - true >|"$outfile" - { - echo "#*** $outfile" - echo "#*** Created: $(date)" - echo "#*** Autogenerated file with expanded #INCLUDEs" - echo "" - } >>"$outfile" - line=0 - while read -r a b ; do - line=$((line + 1)) - if [ "$a" = "#INCLUDE" ] ; then - if [ "X$b" = "X" ] ; then - msg="$hdr <$line> found #INCLUDE with no filename" >>"$outfile" - echo "$msg" >&2 - echo "$msg" >>"$outfile" - else - # expand file name - breal=$(eval echo "$b") - # -r: readable - if [ -r "$breal" ] ; then - { - echo "" - echo "#*** Begin #INCLUDE file: $breal" - cat "$breal" - echo "#*** End #INCLUDE file: $breal" - } >>"$outfile" - else - msg="$hdr <$line> CANNOT READ $breal" - echo "$msg" >&2 - echo "$msg" >>"$outfile" - fi - fi - else - echo "$a $b" >> "$outfile" - fi - done <"$inifile" - echo "$outfile" ;# use the expanded file - return 0 ;# ok -} - function split_app_items () { app_name=$1 shift @@ -453,8 +402,6 @@ function run_applications () { done } -INIFILE="$(handle_includes "$INIFILE")" - # delete directories from path, save name only INI_NAME="${INIFILE##*/}" INI_DIR="${INIFILE%/*}" diff --git a/share/gscreen/skins/silverdragon/silverdragon_handler.py b/share/gscreen/skins/silverdragon/silverdragon_handler.py index 7cbb21be2b0..8f5a96c96f6 100644 --- a/share/gscreen/skins/silverdragon/silverdragon_handler.py +++ b/share/gscreen/skins/silverdragon/silverdragon_handler.py @@ -193,21 +193,21 @@ def initialize_preferences(self): self.gscreen.init_general_pref() self.gscreen.init_theme_pref() self.gscreen.init_window_geometry_pref() - self.no_force_homing = self.gscreen.inifile.find("TRAJ", "NO_FORCE_HOMING") + self.no_force_homing = self.gscreen.inifile.getbool("TRAJ", "NO_FORCE_HOMING", fallback=False) if self.no_force_homing: self.widgets.chk_reload_tool.set_sensitive(False) self.widgets.chk_reload_tool.set_active(False) self.widgets.lbl_reload_tool.set_visible(True) - default_spindle_speed = self.gscreen.inifile.find("DISPLAY", "DEFAULT_SPINDLE_SPEED") + default_spindle_speed = self.gscreen.inifile.getreal("DISPLAY", "DEFAULT_SPINDLE_SPEED") self.spindle_start_rpm = self.gscreen.prefs.getpref( 'spindle_start_rpm', default_spindle_speed, float ) # get the values for the sliders - default_jog_vel = float(self.gscreen.inifile.find("TRAJ", "DEFAULT_LINEAR_VELOCITY")) + default_jog_vel = self.gscreen.inifile.getreal("TRAJ", "DEFAULT_LINEAR_VELOCITY") self.fast_jog = default_jog_vel self.slow_jog = default_jog_vel / self.slow_jog_factor - self.jog_rate_max = self.gscreen.inifile.find("TRAJ", "MAX_LINEAR_VELOCITY") - self.data.spindle_override_max = self.gscreen.inifile.find("DISPLAY", "MAX_SPINDLE_OVERRIDE") - self.data.spindle_override_min = self.gscreen.inifile.find("DISPLAY", "MIN_SPINDLE_OVERRIDE") - self.data.feed_override_max = self.gscreen.inifile.find("DISPLAY", "MAX_FEED_OVERRIDE") + self.jog_rate_max = self.gscreen.inifile.getreal("TRAJ", "MAX_LINEAR_VELOCITY") + self.data.spindle_override_max = self.gscreen.inifile.getreal("DISPLAY", "MAX_SPINDLE_OVERRIDE") + self.data.spindle_override_min = self.gscreen.inifile.getreal("DISPLAY", "MIN_SPINDLE_OVERRIDE") + self.data.feed_override_max = self.gscreen.inifile.getreal("DISPLAY", "MAX_FEED_OVERRIDE") self.data.dro_actual = self.gscreen.inifile.find("DISPLAY", "POSITION_FEEDBACK") # set the slider limits self.widgets.jog_speed.set_range(100, float(self.jog_rate_max) * 60) @@ -226,19 +226,19 @@ def initialize_keybindings(self): self.widgets.window1.connect("key_release_event", self.gscreen.on_key_event, 0) def init_home(self): - self.home_x = self.gscreen.inifile.find("JOINT_0", "HOME") - self.home_y = self.gscreen.inifile.find("JOINT_1", "HOME") - self.home_z = self.gscreen.inifile.find("JOINT_2", "HOME") + self.home_x = self.gscreen.inifile.getreal("JOINT_0", "HOME") + self.home_y = self.gscreen.inifile.getreal("JOINT_1", "HOME") + self.home_z = self.gscreen.inifile.getreal("JOINT_2", "HOME") def init_tool_measurement(self): # set up auto zref - xpos = self.gscreen.inifile.find("TOOLSENSOR", "X") - ypos = self.gscreen.inifile.find("TOOLSENSOR", "Y") - zpos = self.gscreen.inifile.find("TOOLSENSOR", "Z") - sensor_height = self.gscreen.inifile.find("TOOLSENSOR", "SENSOR_HEIGHT") - maxprobe = self.gscreen.inifile.find("TOOLSENSOR", "MAXPROBE") - search_vel = self.gscreen.inifile.find("TOOLSENSOR", "SEARCH_VEL") - probe_vel = self.gscreen.inifile.find("TOOLSENSOR", "PROBE_VEL") + xpos = self.gscreen.inifile.getreal("TOOLSENSOR", "X") + ypos = self.gscreen.inifile.getreal("TOOLSENSOR", "Y") + zpos = self.gscreen.inifile.getreal("TOOLSENSOR", "Z") + sensor_height = self.gscreen.inifile.getreal("TOOLSENSOR", "SENSOR_HEIGHT") + maxprobe = self.gscreen.inifile.getreal("TOOLSENSOR", "MAXPROBE") + search_vel = self.gscreen.inifile.getreal("TOOLSENSOR", "SEARCH_VEL") + probe_vel = self.gscreen.inifile.getreal("TOOLSENSOR", "PROBE_VEL") self.halcomp["probe_vel"] = probe_vel self.halcomp["search_vel"] = search_vel self.halcomp["sensor_height"] = sensor_height @@ -269,8 +269,8 @@ def init_tool_measurement(self): print(_("Block Height - Enabled")) self.widgets.chk_use_auto_zref.emit("toggled") # set up laser crosshair offsets - xpos = self.gscreen.inifile.find("LASER", "X") - ypos = self.gscreen.inifile.find("LASER", "Y") + xpos = self.gscreen.inifile.getreal("LASER", "X") + ypos = self.gscreen.inifile.getreal("LASER", "Y") if not xpos or not ypos: self.widgets.btn_laser_zero.set_sensitive(False) self.widgets.tbtn_laser.set_sensitive(False) diff --git a/share/qtvcp/panels/qtplasmac_sim/qtplasmac_sim_handler.py b/share/qtvcp/panels/qtplasmac_sim/qtplasmac_sim_handler.py index 89727349c6b..8e6ea7632fb 100644 --- a/share/qtvcp/panels/qtplasmac_sim/qtplasmac_sim_handler.py +++ b/share/qtvcp/panels/qtplasmac_sim/qtplasmac_sim_handler.py @@ -42,7 +42,7 @@ def __init__(self, halcomp, widgets, paths): self.w.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) - self.machineName = self.iniFile.find('EMC', 'MACHINE') + self.machineName = self.iniFile.getstring('EMC', 'MACHINE', fallback="") self.styleFile = f'{self.paths.CONFIGPATH}/qtplasmac_sim.qss' self.set_style() self.set_estop() diff --git a/share/qtvcp/screens/qtplasmac/qtplasmac_handler.py b/share/qtvcp/screens/qtplasmac/qtplasmac_handler.py index fc26db9842e..425e0f38d7d 100644 --- a/share/qtvcp/screens/qtplasmac/qtplasmac_handler.py +++ b/share/qtvcp/screens/qtplasmac/qtplasmac_handler.py @@ -180,7 +180,7 @@ def __init__(self, halcomp, widgets, paths): if os.path.isfile(os.path.join(path, 'M190')): self.mPath = 'valid' break - self.machineName = self.iniFile.find('EMC', 'MACHINE') + self.machineName = self.iniFile.getstring('EMC', 'MACHINE', fallback='') self.machineTitle = f'{self.machineName} - QtPlasmaC v{LCNCVER}-{VERSION}, powered by QtVCP and LinuxCNC' self.docsVer = 'devel' if 'pre' in linuxcnc.version else LCNCVER self.prefsFile = os.path.join(self.PATHS.CONFIGPATH, self.machineName + '.prefs') @@ -266,19 +266,19 @@ def __init__(self, halcomp, widgets, paths): 'jog_z_plus', 'jog_z_minus', 'jog_a_plus', 'jog_a_minus', 'jog_b_plus', 'jog_b_minus', 'jog_c_plus', 'jog_c_minus'] self.jogSyncList = [] - self.xMin = float(self.iniFile.find('AXIS_X', 'MIN_LIMIT')) - self.xMax = float(self.iniFile.find('AXIS_X', 'MAX_LIMIT')) - self.yMin = float(self.iniFile.find('AXIS_Y', 'MIN_LIMIT')) - self.yMax = float(self.iniFile.find('AXIS_Y', 'MAX_LIMIT')) - self.zMin = float(self.iniFile.find('AXIS_Z', 'MIN_LIMIT')) - self.zMax = float(self.iniFile.find('AXIS_Z', 'MAX_LIMIT')) + self.xMin = self.iniFile.getreal('AXIS_X', 'MIN_LIMIT') + self.xMax = self.iniFile.getreal('AXIS_X', 'MAX_LIMIT') + self.yMin = self.iniFile.getreal('AXIS_Y', 'MIN_LIMIT') + self.yMax = self.iniFile.getreal('AXIS_Y', 'MAX_LIMIT') + self.zMin = self.iniFile.getreal('AXIS_Z', 'MIN_LIMIT') + self.zMax = self.iniFile.getreal('AXIS_Z', 'MAX_LIMIT') self.xLen = self.xMax - self.xMin self.yLen = self.yMax - self.yMin - self.thcFeedRate = float(self.iniFile.find('AXIS_Z', 'MAX_VELOCITY')) * \ - float(self.iniFile.find('AXIS_Z', 'OFFSET_AV_RATIO')) * 60 - self.offsetFeedRate = min(float(self.iniFile.find('AXIS_X', 'MAX_VELOCITY')) * 30, - float(self.iniFile.find('AXIS_Y', 'MAX_VELOCITY')) * 30, - float(self.iniFile.find('TRAJ', 'MAX_LINEAR_VELOCITYs') or 100000)) + self.thcFeedRate = self.iniFile.getreal('AXIS_Z', 'MAX_VELOCITY') * \ + self.iniFile.getreal('AXIS_Z', 'OFFSET_AV_RATIO') * 60 + self.offsetFeedRate = min(self.iniFile.getreal('AXIS_X', 'MAX_VELOCITY') * 30, + self.iniFile.getreal('AXIS_Y', 'MAX_VELOCITY') * 30, + self.iniFile.getreal('TRAJ', 'MAX_LINEAR_VELOCITY', fallback=100000)) self.maxHeight = self.zMax - self.zMin self.maxPidP = self.thcFeedRate / self.unitsPerMm * 0.1 self.tmpPath = '/tmp/qtplasmac/' @@ -304,7 +304,7 @@ def __init__(self, halcomp, widgets, paths): self.filteredBkp = f'{self.tmpPath}filtered_bkp.ngc' self.oldConvButton = False self.convWidgetsLoaded = False - self.programPrefix = self.iniFile.find('DISPLAY', 'PROGRAM_PREFIX') or os.environ['LINUXCNC_NCFILES_DIR'] + self.programPrefix = self.iniFile.getstring('DISPLAY', 'PROGRAM_PREFIX', fallback=os.environ['LINUXCNC_NCFILES_DIR']) self.dialogError = False self.cutTypeText = '' self.heightOvr = 0.0 @@ -2448,7 +2448,7 @@ def update_check(self): # newest update must be added last in this function # if any writing to the INI file is required then that needs # to be done later in the update_iniwrite function - halfiles = self.iniFile.findall('HAL', 'HALFILE') or None + halfiles = self.iniFile.findall('HAL', 'HALFILE') qtvcpPrefsFile = os.path.join(self.PATHS.CONFIGPATH, 'qtvcp.prefs') self.restart = False # use qtplasmac_comp.hal for component connections (pre V1.221.154 2022/01/18) diff --git a/src/Makefile b/src/Makefile index 49e978e865c..15c9000d13c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -164,7 +164,7 @@ GENERATED_MANPAGES := # Submakefiles from each of these directories will be included if they exist SUBDIRS := \ - libnml/linklist libnml/cms libnml/rcs libnml/inifile libnml/os_intf \ + libnml/linklist libnml/cms libnml/rcs libnml/os_intf \ libnml/nml libnml/buffer libnml/posemath libnml \ \ rtapi/examples/timer rtapi/examples/semaphore rtapi/examples/shmem \ @@ -391,6 +391,8 @@ SRCHEADERS := \ emc/linuxcnc.h \ emc/kinematics/kinematics.h \ emc/motion/emcmotcfg.h \ + emc/ini/inifile.hh \ + emc/ini/inifile.h \ emc/nml_intf/emcpos.h \ emc/nml_intf/emcpose.h \ emc/nml_intf/motion_types.h \ @@ -687,7 +689,7 @@ install-kernel-indep: install-dirs $(EXE) ../scripts/linuxcncmkdesktop $(DESTDIR)$(bindir) $(EXE) ../bin/update_ini $(DESTDIR)$(bindir) $(EXE) ../scripts/halreport $(DESTDIR)$(bindir) - $(FILE) $(filter ../lib/%.a ../lib/%.so.0,$(TARGETS)) $(DESTDIR)$(libdir) + $(FILE) $(filter ../lib/%.a ../lib/%.so.0 ../lib/%.so.1,$(TARGETS)) $(DESTDIR)$(libdir) cp --no-dereference $(wildcard ../lib/*.so) $(DESTDIR)$(libdir) -ldconfig $(DESTDIR)$(libdir) $(FILE) ../lib/linuxcnc/canterp.so $(DESTDIR)$(libdir)/linuxcnc/ @@ -1447,6 +1449,9 @@ objects/var-%: Makefile $(wildcard $(SUBMAKEFILES)) Makefile.inc ../lib/%.so: ../lib/%.so.0 ln -sf $(notdir $<) $@ +../lib/%.so: ../lib/%.so.1 + ln -sf $(notdir $<) $@ + cscope: cscope -Rb diff --git a/src/emc/canterp/Submakefile b/src/emc/canterp/Submakefile index 37e616ae582..470baa61207 100644 --- a/src/emc/canterp/Submakefile +++ b/src/emc/canterp/Submakefile @@ -2,7 +2,7 @@ CANTERPSRCS := emc/canterp/canterp.cc USERSRCS += $(CANTERPSRCS) TARGETS += ../lib/linuxcnc/canterp.so $(call TOOBJSDEPS, $(CANTERPSRCS)) : EXTRAFLAGS=-fPIC -../lib/linuxcnc/canterp.so: $(patsubst %.cc,objects/%.o,$(CANTERPSRCS)) ../lib/liblinuxcncini.so ../lib/librs274.so +../lib/linuxcnc/canterp.so: $(patsubst %.cc,objects/%.o,$(CANTERPSRCS)) ../lib/liblinuxcncini.so.1 ../lib/librs274.so $(ECHO) Linking $(notdir $@) $(Q)mkdir -p ../lib/linuxcnc $(Q)rm -f $@ diff --git a/src/emc/ini/Submakefile b/src/emc/ini/Submakefile index 0ea2b30642d..2b5aad56836 100644 --- a/src/emc/ini/Submakefile +++ b/src/emc/ini/Submakefile @@ -1,4 +1,45 @@ +# The ini-file dynamic library +LIBLCNCINISRCS := emc/ini/inifile.cc +$(call TOOBJSDEPS, $(LIBLCNCINISRCS)) : EXTRAFLAGS=-fPIC +USERSRCS += $(LIBLCNCINISRCS) +TARGETS += ../lib/liblinuxcncini.so ../lib/liblinuxcncini.so.1 + +LIBLCNCINICCINCS = \ + ./emc/ini/inifile.h +LIBLCNCINICXINCS = \ + ./emc/ini/inifile.hh + +# The ini-file headers are needed to build against the liblinuxcncini.so +$(patsubst ./emc/ini/%,../include/%,$(LIBLCNCINICCINCS)): ../include/%.h: ./emc/ini/%.h + cp $^ $@ +$(patsubst ./emc/ini/%,../include/%,$(LIBLCNCINICXINCS)): ../include/%.hh: ./emc/ini/%.hh + cp $^ $@ + +../lib/liblinuxcncini.so.1: $(call TOOBJS,$(LIBLCNCINISRCS)) + $(ECHO) Creating shared library $(notdir $@) + @mkdir -p ../lib + @rm -f $@ + $(Q)$(CXX) $(LDFLAGS) -Wl,-soname,$(notdir $@) -shared -o $@ $^ -lfmt + +# Command-line utility for reading ini-files +INIVALUESRCS := emc/ini/inivalue.cc +USERSRCS += $(INIVALUESRCS) + +../bin/inivalue: $(call TOOBJS, $(INIVALUESRCS)) ../lib/liblinuxcncini.so.1 + $(ECHO) Linking $(notdir $@) + $(Q)$(CXX) $(LDFLAGS) -o $@ $^ -lfmt + +TARGETS += ../bin/inivalue + +# Compatibility script thunking to inivalue +../bin/inivar: emc/ini/inivar + $(ECHO) Copy $(notdir $@) + $(Q)cp -f $^ $@ + +TARGETS += ../bin/inivar + +# Ini-file version updater ../bin/update_ini: emc/ini/update_ini.py @$(ECHO) Syntax checking python script $(notdir $@) $(Q)$(PYTHON) -m py_compile $< diff --git a/src/emc/ini/emcIniFile.cc b/src/emc/ini/emcIniFile.cc deleted file mode 100644 index 4e9de091428..00000000000 --- a/src/emc/ini/emcIniFile.cc +++ /dev/null @@ -1,112 +0,0 @@ -/****************************************************************************** - * - * Copyright (C) 2007 Peter G. Vavaroutsos - * - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2.1 of the GNU General - * Public License as published by the Free Software Foundation. - * This library 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 library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR - * ANY HARM OR LOSS RESULTING FROM ITS USE. IT IS _EXTREMELY_ UNWISE - * TO RELY ON SOFTWARE ALONE FOR SAFETY. Any machinery capable of - * harming persons must have provisions for completely removing power - * from all motors, etc, before persons enter any danger area. All - * machinery must be designed to comply with local and national safety - * codes, and the authors of this software can not, and do not, take - * any responsibility for such compliance. - * - * This code was written as part of the EMC project. For more - * information, go to www.linuxcnc.org. - * - ******************************************************************************/ - -#include // M_PI. -#include "emcIniFile.hh" - - -IniFile::StrIntPair EmcIniFile::jointTypeMap[] = { - {"LINEAR", EMC_LINEAR}, - {"ANGULAR", EMC_ANGULAR}, - { NULL, 0 }, -}; - -EmcIniFile::ErrorCode -EmcIniFile::Find(EmcJointType *result, - const char *tag, const char *section, int num) -{ - return(IniFile::Find(reinterpret_cast(result), jointTypeMap, tag, section, num)); -} - - -IniFile::StrIntPair EmcIniFile::boolMap[] = { - {"TRUE", 1}, - {"YES", 1}, - {"1", 1}, - {"FALSE", 0}, - {"NO", 0}, - {"0", 0}, - { NULL, 0 }, -}; - -EmcIniFile::ErrorCode -EmcIniFile::Find(bool *result, const char *tag, const char *section, int num) -{ - ErrorCode errCode; - int value; - - if((errCode = IniFile::Find(&value, boolMap, tag,section,num)) == ERR_NONE){ - *result = (bool)value; - } - - return(errCode); -} - - -// The next const struct holds pairs for linear units which are -// valid under the [TRAJ] section. These are of the form {"name", value}. -// If the name "name" is encountered in the INI, the value will be used. -EmcIniFile::StrDoublePair EmcIniFile::linearUnitsMap[] = { - { "mm", 1.0 }, - { "metric", 1.0 }, - { "in", 1/25.4 }, - { "inch", 1/25.4 }, - { "imperial", 1/25.4 }, - { NULL, 0 }, -}; - -EmcIniFile::ErrorCode -EmcIniFile::FindLinearUnits(EmcLinearUnits *result, - const char *tag, const char *section, int num) -{ - return(IniFile::Find(result, linearUnitsMap, tag, section, num)); -} - - -// The next const struct holds pairs for angular units which are -// valid under the [TRAJ] section. These are of the form {"name", value}. -// If the name "name" is encountered in the INI, the value will be used. -EmcIniFile::StrDoublePair EmcIniFile::angularUnitsMap[] = { - { "deg", 1.0 }, - { "degree", 1.0 }, - { "grad", 0.9 }, - { "gon", 0.9 }, - { "rad", M_PI / 180 }, - { "radian", M_PI / 180 }, - { NULL, 0 }, -}; - -EmcIniFile::ErrorCode -EmcIniFile::FindAngularUnits(EmcAngularUnits *result, - const char *tag, const char *section, int num) -{ - return(IniFile::Find(result, angularUnitsMap, tag, section, num)); -} diff --git a/src/emc/ini/emcIniFile.hh b/src/emc/ini/emcIniFile.hh deleted file mode 100644 index 6d441635a49..00000000000 --- a/src/emc/ini/emcIniFile.hh +++ /dev/null @@ -1,92 +0,0 @@ -/****************************************************************************** - * - * Copyright (C) 2007 Peter G. Vavaroutsos - * - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2.1 of the GNU General - * Public License as published by the Free Software Foundation. - * This library 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 library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR - * ANY HARM OR LOSS RESULTING FROM ITS USE. IT IS _EXTREMELY_ UNWISE - * TO RELY ON SOFTWARE ALONE FOR SAFETY. Any machinery capable of - * harming persons must have provisions for completely removing power - * from all motors, etc, before persons enter any danger area. All - * machinery must be designed to comply with local and national safety - * codes, and the authors of this software can not, and do not, take - * any responsibility for such compliance. - * - * This code was written as part of the EMC project. For more - * information, go to www.linuxcnc.org. - * - ******************************************************************************/ - -#ifndef _EMCINIFILE_HH_ -#define _EMCINIFILE_HH_ - -#include "nml_intf/emc.hh" -#include "libnml/inifile/inifile.hh" - - -class EmcIniFile : public IniFile { -public: - EmcIniFile(int errMask=0):IniFile(errMask){} - - ErrorCode Find(EmcJointType *result, const char *tag, - const char *section=NULL, int num = 1); - ErrorCode Find(bool *result, const char *tag, - const char *section, int num=1); - ErrorCode FindLinearUnits(EmcLinearUnits *result, - const char *tag, - const char *section=NULL, - int num=1); - ErrorCode FindAngularUnits(EmcAngularUnits *result, - const char *tag, - const char *section=NULL, - int num=1); - - // From base class. - ErrorCode Find(int *result, int min, int max, - const char *tag,const char *section, - int num=1){ - return(IniFile::Find(result, min, max, - tag, section, num)); - } - ErrorCode Find(int *result, const char *tag, - const char *section=NULL, int num = 1){ - return(IniFile::Find(result, - tag, section, num)); - } - ErrorCode Find(double *result, double min, double max, - const char *tag,const char *section, - int num=1){ - return(IniFile::Find(result, min, max, - tag, section, num)); - } - ErrorCode Find(double *result, const char *tag, - const char *section=NULL, int num = 1){ - return(IniFile::Find(result, - tag, section, num)); - } - std::optional Find(const char *tag, const char *section=NULL, - int num = 1){ - return(IniFile::Find(tag, section, num)); - } - -private: - static StrIntPair jointTypeMap[]; - static StrIntPair boolMap[]; - static StrDoublePair linearUnitsMap[]; - static StrDoublePair angularUnitsMap[]; -}; - - -#endif diff --git a/src/emc/ini/iniaxis.cc b/src/emc/ini/iniaxis.cc index 5466d3716f4..90dbe759b58 100644 --- a/src/emc/ini/iniaxis.cc +++ b/src/emc/ini/iniaxis.cc @@ -7,189 +7,132 @@ * Author: * License: GPL Version 2 * System: Linux -* -* Copyright (c) 2004 All rights reserved. +* +* Copyright (c) 2004,2026 All rights reserved. * * Last change: ********************************************************************/ -#include -#include // NULL -#include // atol(), _itoa() -#include // strcmp() -#include // isdigit() -#include -#include +#include #include "nml_intf/emc.hh" +#include "nml_intf/emcglb.h" +#include "nml_intf/emccfg.h" #include "libnml/rcs/rcs_print.hh" -#include "emcIniFile.hh" -#include "iniaxis.hh" // these decls -#include "nml_intf/emcglb.h" // EMC_DEBUG -#include "nml_intf/emccfg.h" // default values for globals +#include "inifile.hh" #include "inihal.hh" +#include "iniaxis.hh" + +using namespace linuxcnc; extern value_inihal_data old_inihal_data; double ext_offset_a_or_v_ratio[EMCMOT_MAX_AXIS]; // all zero // default ratio or external offset velocity,acceleration -#define DEFAULT_A_OR_V_RATIO 0 - -/* - loadAxis(int axis) - - Loads INI file params for axis, axis = X, Y, Z, A, B, C, U, V, W +#define DEFAULT_A_OR_V_RATIO 0.0 - TYPE type of axis (hardcoded: X,Y,Z,U,V,W: LINEAR, A,B,C: ANGULAR) - MAX_VELOCITY max vel for axis - MAX_ACCELERATION max accel for axis - MIN_LIMIT minimum soft position limit - MAX_LIMIT maximum soft position limit +static void inline print_dbg_config(const std::string &s) +{ + if (emc_debug & EMC_DEBUG_CONFIG) { + rcs_print_error("%s", (fmt::format("{}: failed\n", s)).c_str()); + } +} - calls: +// +// Load INI file params for axis [0,8] +// +// Section [AXIS_n] where 'n' is one of [XYZABCUVW]. +// +// [AXIS_n]MIN_LIMIT Minimum soft position limit +// [AXIS_n]MAX_LIMIT Maximum soft position limit +// [AXIS_n]OFFSET_AV_RATIO +// [AXIS_n]MAX_VELOCITY Maximum velocity for axis +// [AXIS_n]MAX_ACCELERATION Maximum acceleration for axis +// [AXIS_n]LOCKING_INDEXER_JOINT +// [AXIS_n]MAX_JERK +// +static int loadAxis(int axis, const IniFile &ini) +{ + if(axis < 0 || axis > 8) { + rcs_print_error("Invalid axis index '%d' outside range [0,8]\n", axis); + return -1; + } - emcAxisSetMinPositionLimit(int axis, double limit); - emcAxisSetMaxPositionLimit(int axis, double limit); - emcAxisSetMaxVelocity(int axis, double vel, double ext_offset_vel); - emcAxisSetMaxAcceleration(int axis, double acc, double ext_offset_acc); - */ + std::string axisSection = fmt::format("AXIS_{}", "XYZABCUVW"[axis]); -static int loadAxis(int axis, EmcIniFile *axisIniFile) -{ - char axisString[16]; - double limit; - double maxVelocity; - double maxAcceleration; - int lockingjnum = -1; // -1 ==> locking joint not used - double maxJerk; - - // compose string to match, axis = 0 -> AXIS_X etc. - switch (axis) { - case 0: snprintf(axisString, sizeof(axisString), "AXIS_X"); break; - case 1: snprintf(axisString, sizeof(axisString), "AXIS_Y"); break; - case 2: snprintf(axisString, sizeof(axisString), "AXIS_Z"); break; - case 3: snprintf(axisString, sizeof(axisString), "AXIS_A"); break; - case 4: snprintf(axisString, sizeof(axisString), "AXIS_B"); break; - case 5: snprintf(axisString, sizeof(axisString), "AXIS_C"); break; - case 6: snprintf(axisString, sizeof(axisString), "AXIS_U"); break; - case 7: snprintf(axisString, sizeof(axisString), "AXIS_V"); break; - case 8: snprintf(axisString, sizeof(axisString), "AXIS_W"); break; + // set min position limit + double limit = ini.findRealV("MIN_LIMIT", axisSection, -1e99); + if (0 != emcAxisSetMinPositionLimit(axis, limit)) { + print_dbg_config("emcAxisSetMinPositionLimit"); + return -1; } + old_inihal_data.axis_min_limit[axis] = limit; - axisIniFile->EnableExceptions(EmcIniFile::ERR_CONVERSION); - - try { - // set min position limit - limit = -1e99; // default - axisIniFile->Find(&limit, "MIN_LIMIT", axisString); - if (0 != emcAxisSetMinPositionLimit(axis, limit)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print_error("bad return from emcAxisSetMinPositionLimit\n"); - } - return -1; - } - old_inihal_data.axis_min_limit[axis] = limit; - - // set max position limit - limit = 1e99; // default - axisIniFile->Find(&limit, "MAX_LIMIT", axisString); - if (0 != emcAxisSetMaxPositionLimit(axis, limit)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print_error("bad return from emcAxisSetMaxPositionLimit\n"); - } - return -1; - } - old_inihal_data.axis_max_limit[axis] = limit; - - ext_offset_a_or_v_ratio[axis] = DEFAULT_A_OR_V_RATIO; - axisIniFile->Find(&ext_offset_a_or_v_ratio[axis], "OFFSET_AV_RATIO", axisString); + // set max position limit + limit = ini.findRealV("MAX_LIMIT", axisSection, 1e99); + if (0 != emcAxisSetMaxPositionLimit(axis, limit)) { + print_dbg_config("emcAxisSetMaxPositionLimit"); + return -1; + } + old_inihal_data.axis_max_limit[axis] = limit; #define REPLACE_AV_RATIO 0.1 #define MAX_AV_RATIO 0.9 - if ( (ext_offset_a_or_v_ratio[axis] < 0) - || (ext_offset_a_or_v_ratio[axis] > MAX_AV_RATIO) - ) { - rcs_print_error("\n Invalid:[%s]OFFSET_AV_RATIO= %8.5f\n" - " Using: [%s]OFFSET_AV_RATIO= %8.5f\n", - axisString,ext_offset_a_or_v_ratio[axis], - axisString,REPLACE_AV_RATIO); - ext_offset_a_or_v_ratio[axis] = REPLACE_AV_RATIO; - } - - // set maximum velocities for axis: vel,ext_offset_vel - maxVelocity = DEFAULT_AXIS_MAX_VELOCITY; - axisIniFile->Find(&maxVelocity, "MAX_VELOCITY", axisString); - if (0 != emcAxisSetMaxVelocity(axis, - (1 - ext_offset_a_or_v_ratio[axis]) * maxVelocity, - ( ext_offset_a_or_v_ratio[axis]) * maxVelocity)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print_error("bad return from emcAxisSetMaxVelocity\n"); - } - return -1; - } - old_inihal_data.axis_max_velocity[axis] = maxVelocity; - - // set maximum accels for axis: acc,ext_offset_acc - maxAcceleration = DEFAULT_AXIS_MAX_ACCELERATION; - axisIniFile->Find(&maxAcceleration, "MAX_ACCELERATION", axisString); - if (0 != emcAxisSetMaxAcceleration(axis, - (1 - ext_offset_a_or_v_ratio[axis]) * maxAcceleration, - ( ext_offset_a_or_v_ratio[axis]) * maxAcceleration)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print_error("bad return from emcAxisSetMaxAcceleration\n"); - } - return -1; - } - old_inihal_data.axis_max_acceleration[axis] = maxAcceleration; - - axisIniFile->Find(&lockingjnum, "LOCKING_INDEXER_JOINT", axisString); - if (0 != emcAxisSetLockingJoint(axis, lockingjnum)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print_error("bad return from emcAxisSetLockingJoint\n"); - } - return -1; - } - - maxJerk = DEFAULT_AXIS_MAX_JERK; - axisIniFile->Find(&maxJerk, "MAX_JERK", axisString); - if (0 != emcAxisSetMaxJerk(axis, maxJerk)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print_error("bad return from emcAxisSetMaxJerk\n"); - } - return -1; - } - old_inihal_data.axis_jerk[axis] = maxJerk; + double ratio = ini.findRealV("OFFSET_AV_RATIO", axisSection, DEFAULT_A_OR_V_RATIO); + if (ratio < 0.0 || ratio > MAX_AV_RATIO) { + rcs_print_error("Invalid: [%s]OFFSET_AV_RATIO=%8.5f (range [0.0,%f]); using: [%s]OFFSET_AV_RATIO=%8.5f\n", + axisSection.c_str(), ratio, MAX_AV_RATIO, axisSection.c_str(), REPLACE_AV_RATIO); + ratio = REPLACE_AV_RATIO; } + ext_offset_a_or_v_ratio[axis] = ratio; + + // set maximum velocities for axis: vel,ext_offset_vel + double maxVelocity = ini.findRealV("MAX_VELOCITY", axisSection, DEFAULT_AXIS_MAX_VELOCITY); + if (0 != emcAxisSetMaxVelocity(axis, + (1 - ext_offset_a_or_v_ratio[axis]) * maxVelocity, + ( ext_offset_a_or_v_ratio[axis]) * maxVelocity)) { + print_dbg_config("emcAxisSetMaxVelocity"); + return -1; + } + old_inihal_data.axis_max_velocity[axis] = maxVelocity; + + // set maximum accels for axis: acc,ext_offset_acc + double maxAcceleration = ini.findRealV("MAX_ACCELERATION", axisSection, DEFAULT_AXIS_MAX_ACCELERATION); + if (0 != emcAxisSetMaxAcceleration(axis, + (1 - ext_offset_a_or_v_ratio[axis]) * maxAcceleration, + ( ext_offset_a_or_v_ratio[axis]) * maxAcceleration)) { + print_dbg_config("emcAxisSetMaxAcceleration"); + return -1; + } + old_inihal_data.axis_max_acceleration[axis] = maxAcceleration; - catch(EmcIniFile::Exception &e){ - e.Print(); + rtapi_s64 jnum = ini.findSIntV("LOCKING_INDEXER_JOINT", axisSection, (rtapi_s64)-1); + if (0 != emcAxisSetLockingJoint(axis, jnum)) { + print_dbg_config("emcAxisSetLockingJoint"); return -1; } + double maxJerk = ini.findRealV("MAX_JERK", axisSection, DEFAULT_AXIS_MAX_JERK); + if (0 != emcAxisSetMaxJerk(axis, maxJerk)) { + print_dbg_config("emcAxisSetMaxJerk"); + return -1; + } + old_inihal_data.axis_jerk[axis] = maxJerk; + return 0; } -/* - iniAxis(int axis, const char *filename) - - Loads INI file parameters for specified axis, [0 .. AXES - 1] - - */ +// +// iniAxis(int axis, const char *filename) +// Loads INI file parameters for specified axis, [0 .. AXES - 1] +// int iniAxis(int axis, const char *filename) { - EmcIniFile axisIniFile(EmcIniFile::ERR_TAG_NOT_FOUND | - EmcIniFile::ERR_SECTION_NOT_FOUND | - EmcIniFile::ERR_CONVERSION); - - if (axisIniFile.Open(filename) == false) { - return -1; - } - - // load its values - if (0 != loadAxis(axis, &axisIniFile)) { + IniFile ini(filename); + if(!ini) return -1; - } - return 0; + + return loadAxis(axis, ini); } diff --git a/src/emc/ini/inifile.cc b/src/emc/ini/inifile.cc new file mode 100644 index 00000000000..2b7da767514 --- /dev/null +++ b/src/emc/ini/inifile.cc @@ -0,0 +1,1708 @@ +// +// IniFile - Ini-file reader and query class +// Copyright (C) 2026 B.Stultiens +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +// FIXME: we don't want to pull in libnml.so +//#include "libnml/rcs/rcs_print.hh" + +#include "inifile.hh" + +using namespace linuxcnc; + +// FIXME: +// This should not be printed directly to stderr, although the previous ini +// reader did that. We also do not want to pull in libnml. A new consistent +// linuxcnc global print library with channels should be established instead. +#include +static inline void print_msg(const std::string &str) +{ + std::cerr << str << std::endl; + //rcs_print((str + "\n").c_str()); +} + +// Identifier characters +static const char STR_ID[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789"; + +namespace linuxcnc { +// +//***************************************************************************** +// IniFileTag: Encapsulate a tag of type 'name = value' +//***************************************************************************** +// +class IniFileTag +{ +public: + IniFileTag() {}; + IniFileTag(const std::string &p, unsigned l, size_t s, const std::string &t, const std::string &v) + : path(p), lineno(l), secidx(s), tagname(t), tagvalue(v) + {} + + std::string path; // From which file it came + unsigned lineno; // Line number of TAG=... + size_t secidx; // Section index locator (index into 'sections') + std::string tagname; + std::string tagvalue; // Content, everything after the '=', but trimmed +}; + +// +//***************************************************************************** +// IniFileSection: Encapsulate a section of type '[section]' +//***************************************************************************** +// +class IniFileSection +{ +public: + IniFileSection() {}; + IniFileSection(const std::string &p, unsigned l, size_t s, const std::string &n) + : path(p), lineno(l), secidx(s), secname(n), tags{} + {} + + std::string path; // From which file it came + unsigned lineno; // Line number of [section] + size_t secidx; // Section index locator (for duplicates) + std::string secname; + // Note: tags in a section can become discontinuous when equally named + // sections are merged. Therefore, we cannot depend on a start + // reference/count and want to register them all. + std::vector tags; // References of tags into IniFileContent::_tags in this section in-order +}; + +// +//***************************************************************************** +// IniFileContent: Ini-file section/tag content and parsing +//***************************************************************************** +// +class IniFileContent +{ +public: + IniFileContent(const std::string &path); + + IniFileContent(const IniFileContent &) = delete; + IniFileContent& operator=(const IniFileContent &) = delete; + + operator bool() const { return _isvalid; } + + int tagCount() const { return _tags.size(); } + int sectionCount() const { return _sections.size(); } + + std::optional getTagRef(int idx) const + { if(idx < 0 || idx >= tagCount()) return std::nullopt; else return &_tags[idx]; } + + std::optional getSectionRef(int idx) const + { if(idx < 0 || idx >= sectionCount()) return std::nullopt; else return &_sections[idx]; } + + std::optional getSectionRef(const std::string &s) const + { if(!hasSection(s)) return std::nullopt; else return &_sections[sectionIndex(s)]; } + +private: + bool hasSection(const std::string &s) const { return _sectionmap.find(s) != _sectionmap.end(); }; + size_t sectionIndex(const std::string &s) const { return _sectionmap.find(s)->second; }; + + std::string _filepath; // The ini-file path of the loaded file + ssize_t _currentsection; // Used during parsing + bool _isvalid; // Set to true on successful parse + + // Actual data container + std::vector _sections; // All section in sequence as found + std::vector _tags; // All tags in sequence as found + + // Map of sections by name indexing 'sections' vector + std::map _sectionmap; + + // File loader and parser functions + bool readFile(const std::string &path, std::string &content); + bool parseLine(const std::string &line, const std::string &path, unsigned linenr); + bool processValue(std::string &value, const std::string &path, unsigned linenr); + bool parseContent(const std::string &content, const std::string &path); + + const size_t MAX_INCLUDE_DEPTH = 16; // Nesting more than 16 deep is an error + bool pathPush(const std::string &path) { _pathstack.push_back(path); return _pathstack.size() < MAX_INCLUDE_DEPTH; } + void pathPop() { if(!_pathstack.empty()) _pathstack.pop_back(); } + bool pathIsLoading(const std::string &path) { for(auto const &s : _pathstack) { if(s == path) return true; } return false; } + std::vector _pathstack; +}; + +} // namespace linuxcnc + +// +// The constructor is the top-level ini-file loader +// +IniFileContent::IniFileContent(const std::string &path) : + _filepath(path), + _currentsection(-1), + _isvalid(false), + _sections{}, + _tags{}, + _sectionmap{}, + _pathstack{} +{ + std::string content; + if(!readFile(path, content)) + return; + + // We now have the complete file read in 'content' + pathPush(path); + bool rv = parseContent(content, path); + pathPop(); + + // Should not happen. Just checking... + if(!_pathstack.empty()) { + print_msg(fmt::format("{}: internal error: Include stack level stuck at {} and should be 0", path, _pathstack.size())); + return; + } + _isvalid = rv; +} + +// +// Convert a hex digit string into its value +// +static bool fromHex(const std::string &str, uint32_t &val) +{ + val = 0; + for(auto ch : str) { + if(!std::isxdigit(ch & 0xff)) + return false; + unsigned cval; + if(std::isdigit(ch)) + cval = ch - '0'; + else + cval = std::toupper(ch) - 'A' + 10; + val <<= 4; + val += cval; + } + return true; +} + +// +// Convert a Unicode code point into a UTF-8 string +// +static std::string toUTF8(int32_t v) +{ + if(v <= 0x7f) { + return std::string(1, (char)v); + } + if(v <= 0x7ff) { + std::string seq; + seq += (char)( (v >> 6) | 0xc0); + seq += (char)((v & 0x3f) | 0x80); + return seq; + } + if(v <= 0xffff) { + std::string seq; + seq += (char)( (v >> 12) | 0xe0); + seq += (char)(((v >> 6) & 0x3f) | 0x80); + seq += (char)(((v >> 0) & 0x3f) | 0x80); + return seq; + } + if(v <= 0x10ffff) { + std::string seq; + seq += (char)( (v >> 18) | 0xf0); + seq += (char)(((v >> 12) & 0x3f) | 0x80); + seq += (char)(((v >> 6) & 0x3f) | 0x80); + seq += (char)(((v >> 0) & 0x3f) | 0x80); + return seq; + } + // This is an invalid code point. Just ignore. + return std::string{}; +} + +// +// Scan the string and determine UTF-8 validity by finding the correct amount +// of high-bit sequences and code point validity. Detects overlong encodings, +// UTF-16 surrogates and code points out-of-range. +// +static bool isValidUTF8(const std::string &s) +{ +#define C(i, m) ((s[i]) & (m)) +#define M(i) ((s[i]) & 0xff) + + for(size_t i = 0; i < s.size(); i++) { + size_t n; + if(C(i, 0x80) == 0x00) { // 0b0bbbbbbb + n = 0; // 0x00-0x7f + } else if(C(i, 0xe0) == 0xc0) { // 0b110bbbbb + if(C(i, 0xfe) == 0xc0) // 0xc0 and 0xc1 + return false; // Overlong encoding, overlaps 0x00-0x7f + n = 1; // 0xc0-0xdf -> 0x000080-0x0007ff + } else if(M(i) == 0xed && i+1 < s.size() && M(i+1) >= 0xa0) { + return false; // 0xd800-0xdfff (UTF-16 surrogates) + } else if(C(i, 0xf0) == 0xe0) { // 0b1110bbbb + if(M(i) == 0xe0 && i+1 < s.size() && M(i+1) < 0xa0) + return false; // Overlong encoding, overlaps 0x000080-0x0007ff + n = 2; // 0xe0-0xef -> 0x000800-0x00ffff + } else if(C(i, 0xf8) == 0xf0) { // 0b11110bbb + if(M(i) == 0xf0 && i+1 < s.size() && M(i+1) < 0x90) + return false; // Overlong encoding, overlaps 0x000800-0x00ffff + if((M(i) > 0xf4) || (M(i) == 0xf4 && i+1 < s.size() && M(i+1) >= 0x90)) + return false; // Results in invalid code points 0x110000-0x1fffff + n = 3; // 0xf0-0xf7 -> 0x010000-0x10ffff + } else { // 0b11111xxx + return false; // 5/6 byte UTF-8 not supported + } + // Test if there are enough bytes following + for(size_t j = 0; j < n && i < s.size(); j++) { + if(++i >= s.size() || C(i, 0xc0) != 0x80) + return false; // No more chars available or not 0b10bbbbbb + } + } + return true; + +#undef M +#undef C +} + +// +// Process a value to find strings +// Modifies the 'value' argument to its final version +// A string value is extracted if the value starts with a single "'" or double +// '"' quote. Otherwise, the value is stripped of optional comments and +// returned. A string may be broken into parts like: +// "foo" "bar" 'zap' "bling" +// and such string will result in returning "foobarzapbling". +// Escape substitution is performed only for double quote enclosed strings. +// Embedding characters with \ooo or \xHH escapes may result in invalid UTF-8 +// strings but that is checked later. Possible escapes: +// - \[abfnrtv] Standard C-type escapes +// - \[0-3][0-7]{0,2} Standard C-type octal escape +// - \x[a-fA-F0-9]{2} Hex 8-bit character escape +// - \u[a-fA-F0-9]{4} UTF-16 escape (with surrogate support) +// - \U[a-fA-F0-9]{8} UTF-32 escape +// +// UTF-16 surrogate support checks high/low surrogate presence and code points +// are converted into UTF-8. UTF-32 code points checked against the valid range +// (code <= 0x10ffff) and converted into UTF-8. +// Embedded NUL characters (\0) are not allowed and flagged as an error. +// +bool IniFileContent::processValue(std::string &value, const std::string &path, unsigned linenr) +{ + if(value.empty()) + return true; + + if(std::string::npos != value.find((char)0)) { + print_msg(fmt::format("{}:{}: error: Embedded literal NUL character not supported", path, linenr)); + return false; + } + + // We should be l/r trimmed getting here. + + if('"' != value[0] && '\'' != value[0]) { + // No string markers at start, still need to strip comment + size_t c = value.find_first_of("#;"); + if(std::string::npos != c) { + // Found comment marker + value.erase(c); // Remove comment + IniFile::rtrim(value); // Remove trailing whitespace + } + if(std::string::npos != value.find((char)0)) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: NUL character not supported in values", path, linenr)); + return false; + } + return true; + } + + // Detected the string marker 'quote'. Need to start collecting enclosed + // strings and parse embedded escapes. + std::string realstr; + uint32_t hexval; + size_t pos; + char quote; + enum { ST_START, ST_COLLECT } state = ST_START; + for(pos = 0; pos < value.size(); pos++) { + char ch = value[pos]; + switch(state) { + case ST_START: + if('"' == ch || '\'' == ch) { + quote = ch; + state = ST_COLLECT; + } else if (!IniFile::isSpace(ch)) { + // Not a space + if('#' == ch || ';' == ch) { + pos = value.size(); // Comment until end-of-line + } else { + // Some junk between strings + // Can only do all enclosed strings or none enclosed + print_msg(fmt::format("{}:{}: error: Expected single or double quote, got '{}' at position {} of the value", + path, linenr, ch, pos+1)); + return false; + } + } // else it is a space and we just ignore it + break; + case ST_COLLECT: + if(ch == quote) { + // End of this string (fragment) + state = ST_START; + break; + } + if(0 == ch) { + // Trying to add a NUL char + print_msg(fmt::format("{}:{}: error: NUL character not supported in quoted values", path, linenr)); + return false; + } + + // For single quote strings we (almost) unconditionally append + if('\'' == quote) { + if('\\' == ch) { + // There must always be a following character when we see + // an escape, regardless what it contains (char at pos is + // 1, next is 2). + if(value.size() - pos < 2) { + print_msg(fmt::format("{}:{}: error: End of line while parsing '\\' escape", path, linenr)); + return false; + } else if('\'' == value[pos+1]) { + // Escapes the single quote + pos++; // Eat the '\\' and add the quote + realstr += '\''; + } else { + realstr += ch; // Just a literal '\\' + } + } else { + realstr += ch; // Just a character + } + break; // We're done when single quoted + } + + // Now we must have a double quoted string, test escapes + if('\\' == ch) { + // An escape, see what follows + size_t cleft = value.size() - pos - 1; // Nr of characters after the '\' + if(cleft < 1) { + // Last char is an escape + // This should never happen because it would have been seen + // as a continuation. The error happens when it fails to + // detect the end quote. + print_msg(fmt::format("{}:{}: internal error: End of line while parsing '\\' escape", path, linenr)); + return false; + } + auto nch = value[pos+1]; // The letter after the escape + switch(nch) { + case '"': + case '\'': + if(quote != nch) { + // Wrongly escaped quote + print_msg(fmt::format("{}:{}: warning: Improper escape of {} quote, ignored", path, linenr, nch)); + } + realstr += nch; + break; + case 'a': realstr += '\a'; break; + case 'b': realstr += '\b'; break; + case 'f': realstr += '\f'; break; + case 'n': realstr += '\n'; break; + case 'r': realstr += '\r'; break; + case 't': realstr += '\t'; break; + case 'v': realstr += '\v'; break; + case '0': // Octal value \0 ... \377 + case '1': + case '2': + case '3': + // Have at least \o, can have \oo or \ooo + hexval = nch - '0'; + if(cleft >= 2 && value[pos+2] >= '0' && value[pos+2] <= '7') { + // Have at least \oo, can have \ooo + hexval <<= 3; + hexval += value[pos+2] - '0'; + if(cleft >= 3 && value[pos+3] >= '0' && value[pos+3] <= '7') { + // Have \ooo + hexval <<= 3; + hexval += value[pos+3] - '0'; + pos++; // Eat the third digit + } + pos++; // Eat the second digit + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded octal NUL character not supported", path, linenr)); + return false; + } + // Note that this can result in invalid UTF-8, but we don't care here + realstr += (char)hexval; + break; + case 'x': // Hex value \xHH + if(cleft < 3+1) { + // Need at least 3 chars for xHH and one for the closing quote + print_msg(fmt::format("{}:{}: error: Improper hex escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 2), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in hex escape", + path, linenr, value.substr(pos+2, 2))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded hex NUL character not supported", path, linenr)); + return false; + } + // Note that this can result in invalid UTF-8, but we don't care here + realstr += (char)hexval; + pos += 2; // Eat the xHH characters + break; + case 'u': // 16-bit hex \uXXXX (actually, this is an UTF-16 abomination) + if(cleft < 5+1) { + // Need at least 5 chars for uXXXX and one for the closing quote + print_msg(fmt::format("{}:{}: error: Improper UTF-16 escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 4), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-16 escape", + path, linenr, value.substr(pos+2, 4))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded UTF-16 NUL character not supported", path, linenr)); + return false; + } + if(hexval >= 0xd800 && hexval <= 0xdfff) { + // We're in UTF-16 surrogate wonderland + if(hexval >= 0xdc00) { + // The high surrogate must be 0xd800..0xdbff + print_msg(fmt::format("{}:{}: error: Invalid high surrogate value u{:04X} in UTF-16", + path, linenr, hexval)); + return false; + } + if(cleft < 5+1 + 6 || '\\' != value[pos+6] || 'u' != value[pos+7]) { + // We need to have room for the second surrogate: uXXXX\uYYYY (plus the closing quote) + print_msg(fmt::format("{}:{}: error: Missing low surrogate in UTF-16", + path, linenr, pos)); + return false; + } + uint32_t hexsur; + if(!fromHex(value.substr(pos+8, 4), hexsur)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in UTF-16 low surrogate escape", + path, linenr, value.substr(pos+8, 4))); + return false; + } + if(hexsur < 0xdc00 || hexsur > 0xdfff) { + // The low surrogate must be 0xdc00..0xdfff + print_msg(fmt::format("{}:{}: error: Invalid low surrogate value u{:04X} in UTF-16", + path, linenr, hexsur)); + return false; + } + hexval = 0x10000 + ((hexval & 0x03ff) << 10) + (hexsur & 0x3ff); + pos += 6; // Eat the entire low surrogate \uYYYY + } + realstr += toUTF8(hexval); + pos += 4; // Eat the XXXX characters + break; + case 'U': // 32-bit hex \UXXXXXXXX (UTF-32) + if(cleft < 9+1) { + // Need at least 9 chars for UXXXXXXXX plus one for the closing quote + print_msg(fmt::format("{}:{}: error: Improper UTF-32 escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 8), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-32 escape", + path, linenr, value.substr(pos+2, 8))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded UTF-32 NUL character not supported", path, linenr)); + return false; + } + if(hexval > 0x10ffff) { + // Invalid code point + print_msg(fmt::format("{}:{}: error: Invalid code point U{:08X} in UTF-32 escape", + path, linenr, hexval)); + return false; + } + realstr += toUTF8(hexval); + pos += 8; // Eat the XXXXXXXX characters + break; + default: + print_msg(fmt::format("{}:{}: warning: Improper escape of '{}', ignored", path, linenr, nch)); + realstr += nch; + break; + } + pos++; // Eat the escape char + } else { + // Not escaped, just copy + realstr += ch; + } + break; + default: + print_msg(fmt::format("{}:{}: internal error: Invalid state '{}' in string collector", path, linenr, (int)state)); + return false; + } + } + if(ST_START != state) { + print_msg(fmt::format("{}:{}: error: Missing terminating {} quote", path, linenr, quote)); + return false; + } + + value = realstr; + return true; +} + +bool IniFileContent::parseLine(const std::string &line, const std::string &path, unsigned linenr) +{ + // Handle include files + if(line.starts_with("#INCLUDE")) { + // Must have a blank after the #INCLUDE directive + if(line.size() <= 8 || !(' ' == line[8] || '\t' == line[8])) { + print_msg(fmt::format("{}:{}: error: Missing filename after #INCLUDE", path, linenr)); + return false; + } + size_t start = line.find_first_not_of(IniFile::STR_WS, 8); // Skip whitespace after #INCLUDE + // #INCLUDE myname.inc + // ^ + // start + if(std::string::npos == start) { + // This should not be possible. If npos, then we skipped all + // whitespace and had whitespace left? Should have detected some + // character because the line is right trimmed before we get here. + print_msg(fmt::format("{}:{}: internal error: Missing filename after #INCLUDE", path, linenr)); + return false; + } + std::string arg = line.substr(start); + IniFile::rtrim(arg); + std::string fname; + if(IniFile::tildeExpand(arg, fname)) { + print_msg(fmt::format("{}:{}: error: Failed to tilde-expand '{}'", path, linenr, arg)); + return false; + } + + // Loading from a subdir means that the includes are relative to it too + size_t pslash = path.find_last_of('/'); + size_t fslash = fname.find_first_of('/'); + if(0 != fslash && std::string::npos != pslash) { + // We have no absolute path in the include filename but we have a + // '/' in the current path. Use the dirname of the current path to + // prepend to the new file to ensure they come from the same + // directory. + fname.insert(0, path.substr(0, pslash + 1)); + } + + // Check the file loading stack if this is being processed to detect + // infinite include loops + if(pathIsLoading(fname)) { + print_msg(fmt::format("{}:{}: error: Include file recursion on loading '{}'", path, linenr, fname)); + return false; + } + std::string content; + if(!readFile(fname, content)) + return false; // Has already printed any message + + // Recurse loading + if(!pathPush(fname)) { + print_msg(fmt::format("{}:{}: error: Maximum include depth ({}) reached on loading '{}'", + path, linenr, MAX_INCLUDE_DEPTH, fname)); + return false; + } + bool rv = parseContent(content, fname); + pathPop(); + return rv; + } + + size_t start = line.find_first_not_of(IniFile::STR_WS); + if(std::string::npos == start) { + // Empty line (only white space) + return true; + } + if('#' == line[start] || ';' == line[start]) { + // Comment until end-of-line + return true; + } + if('[' == line[start]) { + // Section header + // [ SECTION ] + // ^ + // start + size_t end = line.find_first_of("]", start); + if(std::string::npos == end) { + // note: end cannot be 0 because that is occupied by '[' + print_msg(fmt::format("{}:{}: error: Invalid section. Missing ']'", path, linenr)); + return false; + } + // Section header + // [ SECTION ] + // ^ ^ + // start end + std::string sect = line.substr(start+1, end - start - 1); + IniFile::trim(sect); + if(sect.empty()) { + // This happens on '[]' or '[ ]', when no section ID is present + print_msg(fmt::format("{}:{}: error: Invalid section. No content between '[' and ']'", path, linenr)); + return false; + } + if(std::string::npos != sect.find_first_not_of(STR_ID)) { + // There are non ID characters in the section identifier + print_msg(fmt::format("{}:{}: error: Invalid section '{}'. Identifier contains invalid character(s)", + path, linenr, sect)); + return false; + } + if(std::isdigit(sect[0] & 0xff)) { + print_msg(fmt::format("{}:{}: error: Invalid section '{}'. Cannot start with a digit", path, linenr, sect)); + return false; + } + + if(_sectionmap.find(sect) != _sectionmap.end()) { + print_msg(fmt::format("{}:{}: warning: Section '{}' already exists. Merging...", path, linenr, sect)); + _currentsection = _sections[_sectionmap[sect]].secidx; + } else { + _sections.push_back(IniFileSection(path, linenr, _sections.size(), sect)); + _sectionmap[sect] = _sections.size() - 1; + _currentsection = _sections.size() - 1; + } + + // We ignore everything on the line that follows the [section] marker + // But we want to warn... + start = line.find_first_not_of(IniFile::STR_WS, end+1); + if(std::string::npos != start && !('#' == line[start] || ';' == line[start])) { + print_msg(fmt::format("{}:{}: warning: Section header has trailing content, ignored", path, linenr)); + } + return true; + } + + // We must have a tag when we get here. Well, actually, we have a + // non-whitespace character, which should be a tag. + // + // TAG = whatever + // ^ + // start + size_t end = line.find_first_of('=', start); + if(std::string::npos == end) { + // Happens on lines like "VAR" + print_msg(fmt::format("{}:{}: error: Invalid tag '{}'. Expected '=' after tag identifier", + path, linenr, line.substr(start))); + return false; + } + if(end == start) { + // Happens on lines like "= value" + print_msg(fmt::format("{}:{}: error: Invalid tag. Missing identifier before '='", path, linenr)); + return false; + } + // + // TAG = whatever + // ^ ^ + // start end + std::string tag = line.substr(start, end - start); + IniFile::rtrim(tag); + if(std::string::npos != tag.find_first_not_of(STR_ID)) { + // There are non ID characters in the tag identifier + print_msg(fmt::format("{}:{}: error: Invalid tag name '{}'. Identifier contains invalid character(s)", + path, linenr, tag)); + return false; + } + if(std::isdigit(tag[0] & 0xff)) { + print_msg(fmt::format("{}:{}: error: Invalid tag '{}'. Tag identifiers cannot start with a digit", path, linenr, tag)); + return false; + } + + start = line.find_first_not_of(IniFile::STR_WS, end); // Skip whitespace before '=' + if(std::string::npos == start || '=' != line[start]) { + // Happens on lines like "VAR x =..." + print_msg(fmt::format("{}:{}: error: Invalid tag '{}'. Expected '=' after tag identifier", path, linenr, tag)); + return false; + } + + if(_currentsection < 0) { + print_msg(fmt::format("{}:{}: error: Tag '{}' found without prior section definition", path, linenr, tag)); + return false; + } + + // TAG = whatever + // ^ + // end + start = line.find_first_not_of(IniFile::STR_WS, end + 1); // Skip whitespace after '=' + // TAG = whatever + // ^ + // start + std::string value; + if(std::string::npos != start) { + // There was something on the line after '=' + // Copy and rtrim whitespace + value = line.substr(start); + IniFile::rtrim(value); + } // else there was no content after the '=' + + // The 'value' we have now should be left/right trimmed and may be empty. + + // Handle strings and comments + if(!processValue(value, path, linenr)) + return false; + + if(!isValidUTF8(value)) { + print_msg(fmt::format("{}:{}: error: Value contains invalid UTF-8 sequence", path, linenr)); + return false; + } + + // Add the tag + _tags.push_back(IniFileTag(path, linenr, (unsigned)_currentsection, tag, value)); + // Add to the section index + _sections[_currentsection].tags.push_back(_tags.size() - 1); + return true; +} + +bool IniFileContent::parseContent(const std::string &content, const std::string &path) +{ + // Split into lines and parse them + unsigned linenr = 1; + std::string line; + for(size_t pos = 0; pos < content.size();) { + // Search the newline to find a line + size_t eol = content.find_first_of("\n", pos); + if(std::string::npos == eol || eol == content.size()-1) { + // This is EOF with or without terminating newline + line += content.substr(pos); + IniFile::rtrim(line); + if(line[line.size()-1] == '\\') { + // This is a continuation on the last line and there are no + // lines left after this data. A clear error. + print_msg(fmt::format("{}:{}: error: Last line has a continuation", path, linenr)); + return false; + } + return parseLine(line, path, linenr); + } + + // \n this line\r\n + // ^ ^ + // pos eol + // Copy line, without newline + std::string thisline = content.substr(pos, eol - pos); + // Remove trailing whitespace, including possible \r. Note that using + // rtrim here also allows for additional spaces after a continuation, + // but it should be fine to be tolerant and makes it easier for us. + IniFile::rtrim(thisline); + + if(!thisline.empty() && thisline[thisline.size()-1] == '\\') { + // Continuation + thisline.pop_back(); // Remove trailing '\\' + line += thisline; // Add to current line + pos = eol + 1; // Prep for next line + // This will cause continued lines to be counted by the last + // non-continued line. But else we need to do double tracking. + linenr++; + continue; + } else { + line += thisline; + } + + // Not a continuation, we have a whole line to process + if(!parseLine(line, path, linenr)) { + return false; // Message already printed + } + linenr++; + line.clear(); + pos = eol + 1; + } + return true; +} + +bool IniFileContent::readFile(const std::string &path, std::string &content) +{ + // We read in using syscall/C into the string for speed + int fd = ::open(path.c_str(), O_RDONLY); + if(fd < 0) { + print_msg(fmt::format("{}: error: Cannot open ini-file (errno={} ({}))", path, errno, strerror(errno))); + return false; + } + + struct stat sb; + if(fstat(fd, &sb) < 0) { + print_msg(fmt::format("{}: error: Cannot stat ini-file (errno={} ({}))", path, errno, strerror(errno))); + return false; + } + + content.resize(sb.st_size); // Make sure we have room + + ssize_t err; +retry_read: + err = ::read(fd, content.data(), sb.st_size); + if(err < 0) { + if(errno == EINTR) + goto retry_read; + ::close(fd); + print_msg(fmt::format("{}: error: Cannot read ini-file (errno={} ({}))", path, errno, strerror(errno))); + return false; + } + ::close(fd); + if(err != (ssize_t)sb.st_size) { + print_msg(fmt::format("{}: error: Cannot read complete ini-file. Data read ({}) != file size ({})", + path, err, sb.st_size)); + return false; // Couldn't read it all + } + return true; +} + +// +//***************************************************************************** +// IniFileCache: caching singleton +//***************************************************************************** +// +namespace linuxcnc { + +class IniFileCache +{ +private: + IniFileCache() {}; + ~IniFileCache(); + + IniFileCache(const IniFileCache &) = delete; + IniFileCache &operator=(const IniFileCache&) = delete; + + static IniFileCache &getInstance() { + static IniFileCache inst; + return inst; + }; + +public: + static const IniFileContent *getIniFile(const std::string &path); + +private: + // Note that the content are pointers because users can hold a pointer when + // they instantiate IniFile(). That pointer must remain valid throughout + // the lifetime of the IniFile instance and we cannot control that. + // When another thread opens a new file, then the map internals can get + // reallocated and that would invalidate pointers to map entries. + std::map cache; + + // Only one thread can load a file. Otherwise they both could load the same + // one if we get unlucky. + std::mutex locker; +}; + +} // namespace linuxcnc + +IniFileCache::~IniFileCache() +{ + // Release all cached objects from the map + for (auto &kv : cache) { + delete kv.second; + kv.second = nullptr; + } + // The map will subsequently deconstruct itself +} + +const IniFileContent *IniFileCache::getIniFile(const std::string &path) +{ + IniFileCache &ifc = IniFileCache::getInstance(); + std::lock_guard lck(ifc.locker); + + auto c = ifc.cache.find(path); + + if(c != ifc.cache.end()) { + // We have a cached version + return c->second; + } else { + // We don't have it in our cache, load it + IniFileContent *x = new IniFileContent(path); + if(!*x) { + // Load resulted in an error. Get rid of it again. + delete x; + return nullptr; + } + // Got it loaded. Now add to the cache and return it + ifc.cache[path] = x; + return x; + } +} + +// +//***************************************************************************** +// IniFile class +//***************************************************************************** +// +IniFile::IniFile(const std::string &path) + : _inifilecontent(nullptr), + _filepath{} +{ + Open(path); +} + +bool IniFile::Open(const std::string &path) +{ + Close(); + if(auto f = IniFileCache::getIniFile(path)) { + _inifilecontent = f; + _filepath = path; + return true; + } + return false; +} + +bool IniFile::hasOpenError(const std::string &tag, const std::string §ion) const +{ + if(!isOpen()) { + print_msg(fmt::format("{}: error: Attempt to extract '[{}]{}' from invalid or not loaded ini-file.", + _filepath, section, tag)); + return false; + } + return true; +} + +// +// Find all the tags with name 'tag' in (optional) section 'section' and return +// references as a vector. The std::nullopt value is returned upon error. +// +std::optional> IniFile::findTags(const std::string &tag, const std::string §ion) const +{ + if(!hasOpenError(tag, section)) + return std::nullopt; + + std::vector tags; + + if(tag.empty() && section.empty()) { + // This returns all tags from all sections in sequence + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + tags.push_back(*t); + } else { + break; + } + } + return tags; + } + + if(tag.empty()) { + // Return all the section's values + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick all tags + for(auto t : (*sect)->tags) { // Linked by index in master _tags + if(auto tp = _inifilecontent->getTagRef(t)) { + tags.push_back(*tp); + } + } + } + return tags; + } + + if(section.empty()) { + // Return all values matching tag's name + // Loop over all tags and find those with the proper name + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + if((*t)->tagname == tag) + tags.push_back(*t); + } else { + // We get a std::nullopt when the list is done + // Return what we gathered + return tags; + } + } + } + + // Find the section + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick the all tags of the requested name + for(auto t : (*sect)->tags) { // Linked by index in master _tags + if(auto tp = _inifilecontent->getTagRef(t)) { + if((*tp)->tagname == tag) { + tags.push_back(*tp); + } + } + } + } + return tags; +} + +// +// Find the num'th 'tag' in (optional) section 'section' and return a reference +// to that tag. The std::nullopt is returned upon error or if the tag is not +// found. +// +std::optional IniFile::findTag(const std::string &tag, const std::string §ion, int num) const +{ + if(!hasOpenError(tag, section)) + return std::nullopt; + + if(section.empty()) { + // Must have a count if no section + if(num < 1) + return std::nullopt; + // Loop over all tags and find those with the proper name + // Empty tag means any (so take the num'th). + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + if((tag.empty() || (*t)->tagname == tag) && !--num) + return *t; + } else { + return std::nullopt; + } + } + } + + if(num < 1) + num = 1; + // Find the section + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick the num'th tag of the requested name + // With an empty tag just take the num'th + for(auto t : (*sect)->tags) { + if(auto tp = _inifilecontent->getTagRef(t)) { + if((tag.empty() || (*tp)->tagname == tag) && !--num) { + return *tp; + } + } + } + } + + return std::nullopt; +} + +// +// Find 'section' and return a reference to it. The std::nullopt is returned +// upon error or if the section is not found. +// Empty section names are *not* valid. +// +std::optional IniFile::findSection(const std::string §ion) const +{ + if(section.empty() || !hasOpenError({}, section)) + return std::nullopt; + + return _inifilecontent->getSectionRef(section); +} + +// +// Return the path and line number of a specified tag or empty/-1 if not found +// +std::pair IniFile::lineOf(int num, const std::string &tag, const std::string §ion) const +{ + if(auto t = findTag(tag, section, num)) + return {(*t)->path, (*t)->lineno}; + return {{}, -1}; +} + +// +// Helper to get the section name from a tag +// +std::string IniFile::sectionFromTag(const IniFileTag *val) const +{ + if(auto sect = _inifilecontent->getSectionRef(val->secidx)) + return (*sect)->secname; + return ""; +} + +// +// Standard conversion routines +// bool - TRUE/YES/1/ON and FALSE/NO/0/OFF (case insensitive) +// s64 - signed value with optional sign and radix prefix +// u64 - signed value with optional sign and radix prefix +// real - floating point value with optional sign and exponent +// +// These routines return std::nullopt when a conversion fails and an +// appropriate message is emitted. +// +std::optional IniFile::convertBool(const std::string &val) +{ + static const std::map booleanMap = { + { "true", true }, + { "yes", true }, + { "1", true }, + { "on", true }, + { "false", false }, + { "no", false }, + { "0", false }, + { "off", false }, + }; + auto const b = booleanMap.find(val); // Case-insensitive map search + if(b != booleanMap.end()) + return b->second; + return std::nullopt; +} + +std::optional IniFile::convertBool(const IniFileTag *val) const +{ + if(auto b = IniFile::convertBool(val->tagvalue)) + return *b; + + print_msg(fmt::format("{}:{}: error: Invalid boolean value [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, val->tagvalue)); + + return std::nullopt; +} + +// +// Helper to get different radix numbers parsed properly +// Supported: +// * [+-]?[0-9]+ Decimal +// * [+-]?0[xX][0-9a-fA-F]+ Hexadecimal +// * [+-]?0[oO][0-7]+ Octal +// * [+-]?0[bB][0-1]+ Binary +// +static int radixAndPrefix(std::string &val) +{ + unsigned pm = 0; // Default no +/- + + int base = 10; // Default to decimal + + // String size must be 3 or more for alternate base values. Two for the + // prefix and at least one digit. A leading +/- may also be present. + // We know that the value from the tag has the leading whitespace removed. + if(val.size() > 1 && ('-' == val[0] || '+' == val[0])) { + pm = 1; + } + + // Detect: [+-]?0[xXoObB]. + if(val.size() > pm+2 && '0' == val[pm]) { + // Set the radix and remove the prefix + switch(val[pm+1]) { + case 'x': case 'X': base = 16; val.erase(pm, 2); break; + case 'o': case 'O': base = 8; val.erase(pm, 2); break; + case 'b': case 'B': base = 2; val.erase(pm, 2); break; + } + } + return base; +} + + +std::optional IniFile::convertSInt(const std::string &_val) +{ + std::string tval = _val; + int base = radixAndPrefix(tval); + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg("internal error: ConvertSInt(): Cannot set locale to \"C\" for strtoll"); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_s64 i = strtoll(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("warning: Trailing character(s) in signed integer conversion of '{}'", tval)); + } + return i; +} + +std::optional IniFile::convertSInt(const IniFileTag *val) const +{ + std::string tval = val->tagvalue; + int base = radixAndPrefix(tval); + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg(fmt::format("{}:{}: internal error: Cannot set locale to \"C\" for strtoll", val->path, val->lineno)); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_s64 i = strtoll(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + print_msg(fmt::format("{}:{}: error: Invalid signed integer [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("{}:{}: warning: Trailing character(s) in signed integer conversion ([{}]{}='{}')", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + } + return i; +} + +std::optional IniFile::convertUInt(const std::string &_val) +{ + std::string tval = IniFile::trimcpy(_val); + int base = radixAndPrefix(tval); + char *eptr; + + if(!tval.empty() && tval[0] == '-') { + print_msg(fmt::format("warning: Unsigned integer conversion detected a leading minus sign (-)", tval)); + } + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg("internal error: ConvertUInt(): Cannot set locale to \"C\" for strtoull"); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_u64 u = strtoull(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("warning: Trailing character(s) in unsigned integer conversion of '{}')", tval)); + } + return u; +} + +std::optional IniFile::convertUInt(const IniFileTag *val) const +{ + std::string tval = val->tagvalue; + int base = radixAndPrefix(tval); + char *eptr; + + if(!tval.empty() && tval[0] == '-') { + print_msg(fmt::format("{}:{}: warning: Unsigned integer conversion detected a leading minus sign (-)", + val->path, val->lineno, tval)); + } + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg(fmt::format("{}:{}: internal error: Cannot set locale to \"C\" for strtoull", val->path, val->lineno)); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_u64 u = strtoull(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + print_msg(fmt::format("{}:{}: error: Invalid unsigned integer [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("{}:{}: warning: Trailing character(s) in unsigned integer conversion ([{}]{}='{}')", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + } + return u; +} + +std::optional IniFile::convertReal(const std::string &val) +{ + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg("internal error: ConvertReal(): Cannot set locale to \"C\" for strtod"); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + double r = strtod(val.c_str(), &eptr); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == val.c_str() || errnosave != 0) { + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("warning: Trailing character(s) in floating point conversion of '{}')", val)); + } + return r; +} + +std::optional IniFile::convertReal(const IniFileTag *val) const +{ + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg(fmt::format("{}:{}: internal error: Cannot set locale to \"C\" for strtod", val->path, val->lineno)); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + double r = strtod(val->tagvalue.c_str(), &eptr); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == val->tagvalue.c_str() || errnosave != 0) { + print_msg(fmt::format("{}:{}: error: Invalid floating point [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, val->tagvalue)); + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("{}:{}: warning: Trailing character(s) in floating point conversion ([{}]{}='{}')", + val->path, val->lineno, sectionFromTag(val), val->tagname, val->tagvalue)); + } + return r; +} + +// +// Find the num'th instance of '[section]tag' with optional 'section' +// +std::optional IniFile::findString(int num, const std::string &tag, const std::string §ion) const +{ + if(auto t = findTag(tag, section, num)) { + return (*t)->tagvalue; + } + + return std::nullopt; +} + +std::optional IniFile::findBool(int num, const std::string &tag, const std::string §ion) const +{ + if(auto t = findTag(tag, section, num)) { + return convertBool(*t); + } + return std::nullopt; +} + +// +// Find all instances of a '[section]name' with optional 'section' +// +std::vector IniFile::findStringAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) + vals.push_back(c->tagvalue); + } + return vals; +} + +std::vector IniFile::findBoolAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertBool(c)) + vals.push_back(*b); + } + } + return vals; +} + +std::vector IniFile::findSIntAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertSInt(c)) + vals.push_back(*b); + } + } + return vals; +} + +std::vector IniFile::findUIntAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertUInt(c)) + vals.push_back(*b); + } + } + return vals; +} + +std::vector IniFile::findRealAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertReal(c)) + vals.push_back(*b); + } + } + return vals; +} + +// +// Find the num'th instance of '[section]name' with optional 'section' and +// convert to value. +// Optionally limited to range: mini <= value <= maxi +// +std::optional IniFile::findSInt(int num, const std::string &tag, const std::string §ion, rtapi_s64 mini, rtapi_s64 maxi) const +{ + if(auto t = findTag(tag, section, num)) { + if(auto v = convertSInt(*t)) { + if(*v >= mini && *v <= maxi) { + return v; + } + } + } + return std::nullopt; +} + +std::optional IniFile::findUInt(int num, const std::string &tag, const std::string §ion, rtapi_u64 mini, rtapi_u64 maxi) const +{ + if(auto t = findTag(tag, section, num)) { + if(auto v = convertUInt(*t)) { + if(*v >= mini && *v <= maxi) { + return v; + } + } + } + return std::nullopt; +} + +std::optional IniFile::findReal(int num, const std::string &tag, const std::string §ion, double mini, double maxi) const +{ + if(auto t = findTag(tag, section, num)) { + if(auto v = convertReal(*t)) { + if(*v >= mini && *v <= maxi) { + return v; + } + } + } + return std::nullopt; +} + +// +// Section support: Find all section and return to user +// +std::vector IniFile::findSections() const +{ + std::vector sects; + + for(size_t i = 0; true; i++) { + if(auto const s = _inifilecontent->getSectionRef(i)) { + sects.push_back((*s)->secname); + } else { + break; + } + } + return sects; +} + +// +// Variables support: Find all variables in an optional section and return to user +// +std::vector> IniFile::findVariables(const std::string §ion) const +{ + std::vector> vars; + + if(section.empty()) { + // Without section return all variable names from the master pool + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + vars.push_back({(*t)->tagname, (*t)->tagvalue}); + } else { + break; + } + } + } else { + // If a section is present, only return those variable names + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick all tags + for(auto t : (*sect)->tags) { // Linked by index in master _tags + if(auto tp = _inifilecontent->getTagRef(t)) { + vars.push_back({(*tp)->tagname, (*tp)->tagvalue}); + } + } + } + } + return vars; +} + +// +// Expand ~/filename to $HOME/filename +// +int IniFile::tildeExpand(const std::string &path, std::string &result) +{ + if(path.size() < 2 || '~' != path[0] || '/' != path[1]) { + // Does not start with "~/", so we do not expand + result = path; // Just copy + return 0; + } + + const char *home = getenv("HOME"); + if(!home) + return -ENOENT; + + result = std::string(home) + path.substr(1); + return 0; +} + +// +//***************************************************************************** +// C-API interface routines +//***************************************************************************** +// +extern "C" { + +int iniFindString(const char *inipath, const char *tag, const char *section, char *buf, size_t bufsize) +{ + if(!inipath || !tag || !buf || !bufsize) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findString(tag, section)) { + if(v->size() >= bufsize) + return -ENOSPC; // buffer cannot hold string + nul char + strcpy(buf, v->c_str()); + return 0; + } + return -ENOENT; +} + +int iniFindBool(const char *inipath, const char *tag, const char *section, bool *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findBool(tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +int iniFindSInt(const char *inipath, const char *tag, const char *section, rtapi_s64 *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findSInt(1, tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +int iniFindUInt(const char *inipath, const char *tag, const char *section, rtapi_u64 *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findUInt(1, tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +int iniFindDouble(const char *inipath, const char *tag, const char *section, double *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findReal(1, tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +// Compatibility function +int iniFindInt(const char *inipath, const char *tag, const char *section, int *result) +{ + rtapi_s64 val; + if(int err = iniFindSInt(inipath, tag, section, &val)) + return err; + *result = (int)val; + return 0; +} + +int TildeExpansion(const char *path, char *buf, size_t bufsize) +{ + std::string p; + if(int err = IniFile::tildeExpand(path, p)) + return err; + + if(p.size() >= bufsize) + return -ENOSPC; // buffer cannot hold string + nul char + + strcpy(buf, p.c_str()); + return 0; +} + +// +// Split string 'str' into tokens using 'delim' as delimiters +// +std::vector IniFile::split(const std::string &delim, const std::string &str) +{ + std::vector toks; + size_t start = str.find_first_not_of(delim); // Start-of-token pos (or npos if only delimters) + size_t end = str.find_first_of(delim, start); // End-of-token pos+1 (or npos if last token) + + // While start and end positions are available (meaning there is a token) + while (!(std::string::npos == end && std::string::npos == start)) { + toks.push_back(str.substr(start, end - start)); // Copy token into vector + start = str.find_first_not_of(delim, end); // Find new start-of-token from old end + end = str.find_first_of(delim, start); // and end-of-token + } + return toks; +} + +} // extern "C" + +// vim: ts=4 sw=4 diff --git a/src/emc/ini/inifile.h b/src/emc/ini/inifile.h new file mode 100644 index 00000000000..8973bd708fb --- /dev/null +++ b/src/emc/ini/inifile.h @@ -0,0 +1,61 @@ +// +// IniFile - Ini-file reader and query class +// Copyright (C) 2026 B.Stultiens +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#ifndef __LINUXCNC_INI_INIFILE_H +#define __LINUXCNC_INI_INIFILE_H + +#ifdef __cplusplus +#warning "Including inifile.h in C++ code is inefficient. You should use the C++ API in inifile.hh instead." +#endif + +#include +#include +#include + +#include + +// +// C-API interface functions +// +#ifdef __cplusplus +extern "C" { +#endif + +// There is no real limit in the C++ version. This value is for compatibility. +// It has been increased from the original 256 to PATH_MAX to allow for full +// paths to be properly encapsulated. +#define INI_MAX_LINELEN PATH_MAX + +int TildeExpansion(const char *file, char *path, size_t size); + +int iniFindString(const char *inipath, const char *tag, const char *section, char *buf, size_t bufsize); +int iniFindBool(const char *inipath, const char *tag, const char *section, bool *result); +int iniFindSInt(const char *inipath, const char *tag, const char *section, rtapi_s64 *result); +int iniFindUInt(const char *inipath, const char *tag, const char *section, rtapi_u64 *result); +int iniFindDouble(const char *inipath, const char *tag, const char *section, double *result); + +// Compatibility with existing code +// Maps to iniFindSInt() and truncates the result +int iniFindInt(const char *inipath, const char *tag, const char *section, int *result); + +#ifdef __cplusplus +} +#endif + +#endif +// vim: ts=4 sw=4 diff --git a/src/emc/ini/inifile.hh b/src/emc/ini/inifile.hh new file mode 100644 index 00000000000..76a325bd917 --- /dev/null +++ b/src/emc/ini/inifile.hh @@ -0,0 +1,444 @@ +// +// IniFile - Ini-file reader and query class +// Copyright (C) 2026 B.Stultiens +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#ifndef __LINUXCNC_INI_INIFILE_HH +#define __LINUXCNC_INI_INIFILE_HH + +#ifndef __cplusplus +#error "inifile.hh cannot be used in C. Please use the C-API in inifile.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// +// IniFile public methods for extraction of values from the ini-file: +// +// Note that selecting the num'th value is the first argument to prevent +// accidents using wrong arguments for the bool/integer/real versions. This way +// you have to select the num'th as the first argument if you really mean to +// use it and cannot be mistaken as the default or min/max values. You can +// just omit the num if you just want the first value as a convenience method. +// +// All find_X_All() methods, except findStringAll(), will perform conversion to +// the requested type. That also means that mixed value content may result in +// errors and dropped results. Use the find_X_All() methods only when you know +// that the values are all supposed to be of the same type. +// +// Helper methods can be used to query the ini-file to get information about +// sections, variables, paths and line numbers: +// bool hasSection(section) +// bool hasVariable(variable, section) +// bool hasVariable(num, variable, section) +// which all map to: +// bool isSet(variable, section) +// bool isSet(num, variable, section) +// +// Ini-file structural content methods: +// std::vector findSections() +// std::vector> findVariables(section); +// std::pair lineOf(variable, section) +// std::pair lineOf(num, variable, section) +// +// General value extraction methods: +// std::vector findStringAll(tag, section) +// std::optional findString(tag, section) +// std::optional findString(num, tag, section) +// std::string findStringV(tag, section, def) +// std::string findStringV(num, tag, section, def) +// +// std::vector findBoolAll(tag, section) +// std::optional findBool(tag, section) +// std::optional findBool(num, tag, section) +// bool findBoolV(tag, section, def) +// bool findBoolV(num, tag, section, def) +// +// std::vector findSIntAll(tag, section) +// std::optional findSInt(tag, section, mini = INT64_MIN, maxi = INT64_MAX) +// std::optional findSInt(num, tag, section, mini = INT64_MIN, maxi = INT64_MAX) +// rtapi_s64 findSIntV(tag, section, def, mini = INT64_MIN, maxi = INT64_MAX) +// rtapi_s64 findSIntV(num, tag, section, def, mini = INT64_MIN, maxi = INT64_MAX) +// +// std::vector findUIntAll(tag, section) +// std::optional findUInt(tag, section, mini = 0, maxi = UINT64_MAX) +// std::optional findUInt(num, tag, section, mini = 0, maxi = UINT64_MAX) +// rtapi_u64 findUIntV(tag, section, def, mini = 0, maxi = UINT64_MAX) +// rtapi_u64 findUIntV(num, tag, section, def, mini = 0, maxi = UINT64_MAX) +// +// std::vector findRealAll(tag, section) +// std::optional findReal(tag, section, mini = -DBL_MAX, maxi = +DBL_MAX) +// std::optional findReal(num, tag, section, mini = -DBL_MAX, maxi = +DBL_MAX) +// double findRealV(tag, section, def, mini = -DBL_MAX, maxi = +DBL_MAX) +// double findRealV(num, tag, section, def, mini = -DBL_MAX, maxi = +DBL_MAX) +// +// Convenience methods using the (usually 32-bit) integer type are provided and +// map to findSInt: +// std::optional findInt(tag, section, mini = INT_MIN, maxi = INT_MAX) +// std::optional findInt(num, tag, section, mini = INT_MIN, maxi = INT_MAX) +// int findIntV(tag, section, def, mini = INT_MIN, maxi = INT_MAX) +// int findIntV(num, tag, section, def, mini = INT_MIN, maxi = INT_MAX) +// +// Implementing (case [in]sensitive) list type values can be done using the +// IniFile::findMap() template function for case sensitive and case insensitive +// compares. The map defined for type T mapping: +// const std::map = {...} +// const std::map = {...} +// for function: +// T findCustom(const IniFile &ini, const std::string &tag, const std::string §ion, T def) +// +// Example: +// double findUnits(const IniFile &ini, const std::string &tag, const std::string §ion, double def) +// { +// static const std::map unitsMap = { +// { "mm", 1.0 }, +// { "metric", 1.0 }, +// { "in", 1/25.4 }, +// { "inch", 1/25.4 }, +// { "imperial", 1/25.4 }, +// }; +// +// if(auto c = ini.findMap(unitsMap, tag, section)) +// return *c; +// return def; +// } +// + +namespace linuxcnc { + +// Forward declaration of internal classes +class IniFileContent; +class IniFileTag; +class IniFileSection; + +// +// Public facing IniFile operations +// +class IniFile +{ +public: + IniFile(const std::string &filePath); + + operator bool() const { return isOpen(); } + bool isOpen() const { return _inifilecontent != nullptr; } + + bool hasSection(const std::string §ion) const { + return isSet(1, "", section); + } + bool hasVariable(int num, const std::string &tag, const std::string §ion) const { + return isSet(num, tag, section); + } + bool hasVariable(const std::string &tag, const std::string §ion) const { + return hasVariable(1, tag, section); + } + + // Returns true if the specified [section]tag is present + bool isSet(int num, const std::string &tag, const std::string §ion) const { + if(tag.empty() && section.empty()) { + // No, we don't have nothing + return false; + } + if(tag.empty()) { + // We can have a section that has no variables in it + return (bool)findSection(section); + } + return (bool)findTag(tag, section, num); + } + bool isSet(const std::string &tag, const std::string §ion) const { + return isSet(1, tag, section); + } + + // Returns the ini-file path and line number of the specified variable + std::pair lineOf(int num, const std::string &tag, const std::string §ion) const; + std::pair lineOf(const std::string &tag, const std::string §ion) const { + return lineOf(1, tag, section); + } + + // Get all variables named 'tag' from (optional) section in a vector. + // Returns an empty vector if none found. + std::vector findStringAll(const std::string &tag, const std::string §ion) const; + std::vector findBoolAll(const std::string &tag, const std::string §ion) const; + std::vector findSIntAll(const std::string &tag, const std::string §ion) const; + std::vector findUIntAll(const std::string &tag, const std::string §ion) const; + std::vector findRealAll(const std::string &tag, const std::string §ion) const; + + // Get the num'th variable named 'tag' from (optional) section. + // Returns std::nullopt if not found + std::optional findString(int num, const std::string &tag, const std::string §ion) const; + std::optional findBool(int num, const std::string &tag, const std::string §ion) const; + + std::optional findString(const std::string &tag, const std::string §ion) const { + return findString(1, tag, section); + } + std::optional findBool(const std::string &tag, const std::string §ion) const { + return findBool(1, tag, section); + } + + // Get numerical values with options bounded ranges. + // Returns std::nullopt if not found or out-of-range. + std::optional findSInt(int num, const std::string &tag, const std::string §ion, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const; + std::optional findUInt(int num, const std::string &tag, const std::string §ion, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const; + std::optional findReal(int num, const std::string &tag, const std::string §ion, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const; + + std::optional findSInt(const std::string &tag, const std::string §ion, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const { + return findSInt(1, tag, section, mini, maxi); + } + std::optional findUInt(const std::string &tag, const std::string §ion, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const { + return findUInt(1, tag, section, mini, maxi); + } + std::optional findReal(const std::string &tag, const std::string §ion, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const { + return findReal(1, tag, section, mini, maxi); + } + + // Get the num'th value with defaults if not found. + std::string findStringV(int num, const std::string &tag, const std::string §ion, const std::string &def) const { + if(auto v = findString(num, tag, section)) + return *v; + return def; + } + bool findBoolV(int num, const std::string &tag, const std::string §ion, bool def) const { + if(auto v = findBool(num, tag, section)) + return *v; + return def; + } + + std::string findStringV(const std::string &tag, const std::string §ion, const std::string &def) const { + return findStringV(1, tag, section, def); + } + bool findBoolV(const std::string &tag, const std::string §ion, bool def) const { + return findBoolV(1, tag, section, def); + } + + // Find the num'th value within min/max range. + // Returns default value if not found or out-of-range. + // These have a suffix 'V' to counter the overloading ambiguity. + rtapi_s64 findSIntV(int num, const std::string &tag, const std::string §ion, rtapi_s64 def, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const { + if(auto v = findSInt(num, tag, section, mini, maxi)) + return *v; + return def; + } + rtapi_u64 findUIntV(int num, const std::string &tag, const std::string §ion, rtapi_u64 def, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const { + if(auto v = findUInt(num, tag, section, mini, maxi)) + return *v; + return def; + } + double findRealV(int num, const std::string &tag, const std::string §ion, double def, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const { + if(auto v = findReal(num, tag, section, mini, maxi)) + return *v; + return def; + } + + rtapi_s64 findSIntV(const std::string &tag, const std::string §ion, rtapi_s64 def, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const { + return findSIntV(1, tag, section, def, mini, maxi); + } + rtapi_u64 findUIntV(const std::string &tag, const std::string §ion, rtapi_u64 def, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const { + return findUIntV(1, tag, section, def, mini, maxi); + } + double findRealV(const std::string &tag, const std::string §ion, double def, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const { + return findRealV(1, tag, section, def, mini, maxi); + } + + // Convenience methods + std::optional findInt(int num, const std::string &tag, const std::string §ion, + int mini = INT_MIN, int maxi = INT_MAX) const { + if(auto v = findSInt(num, tag, section, mini, maxi)) + return (int)*v; + return std::nullopt; + } + std::optional findInt(const std::string &tag, const std::string §ion, + int mini = INT_MIN, int maxi = INT_MAX) const { + return findInt(1, tag, section, mini, maxi); + } + int findIntV(int num, const std::string &tag, const std::string §ion, int def, + int mini = INT_MIN, int maxi = INT_MAX) const { + return (int)findSIntV(num, tag, section, def, mini, maxi); + } + int findIntV(const std::string &tag, const std::string §ion, int def, + int mini = INT_MIN, int maxi = INT_MAX) const { + return findIntV(1, tag, section, def, mini, maxi); + } + + // Map-search matching of values returning the mapped value. + // Search is case-sensitive. + template + std::optional findMap(int num, const std::map &map, + const std::string &tag, const std::string §ion = "") const { + if(auto s = findString(num, tag, section)) { + auto const m = map.find(*s); + if(m != map.end()) { + return m->second; + } + } + return std::nullopt; + } + + template + std::optional findMap(const std::map &map, + const std::string &tag, const std::string §ion = "") const { + return findMap(1, map, tag, section); + } + // Map compare function without case + struct caseless { + struct caseless_cmp { + bool operator() (const char &a, const char &b) const { + return std::tolower(a & 0xff) < std::tolower(b & 0xff); + } + }; + bool operator() (const std::string &a, const std::string &b) const { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), caseless_cmp()); + } + }; + + // Map-search matching of values returning the mapped value. + // Search is case-insensitive. + template + std::optional findMap(int num, const std::map &map, + const std::string &tag, const std::string §ion = "") const { + if(auto s = findString(num, tag, section)) { + auto const m = map.find(*s); + if(m != map.end()) { + return m->second; + } + } + return std::nullopt; + } + + template + std::optional findMap(const std::map &map, + const std::string &tag, const std::string §ion = "") const { + return findMap(1, map, tag, section); + } + + // Return a list of section names from the ini-file + std::vector findSections() const; + // Return a list of variable name/value pairs from an optional section in the ini-file + std::vector> findVariables(const std::string §ion) const; + + // The fact that this is here is because of compatibility + // Perform tilde expansion using HOME environment variable. + // Returns the filePath "~/path" as "$HOME/path" + // Zero is returned on success or a negative value (-errno) on failure. + static int tildeExpand(const std::string &filePath, std::string &res); + + // Compatibility method + static int TildeExpansion(const std::string &filePath, std::string &res) { + return IniFile::tildeExpand(filePath, res); + } + + static std::optional convertBool(const std::string &val); + static std::optional convertSInt(const std::string &val); + static std::optional convertUInt(const std::string &val); + static std::optional convertReal(const std::string &val); + + // split() Tokenize 'str' based on 'delim' + static std::vector split(const std::string &delim, const std::string &str); + + // Trim leading/trailing or both + static void rtrim(std::string &str) { + size_t n = str.find_last_not_of(IniFile::STR_WS); + if(std::string::npos != n) + str.erase(n+1); + } + static void ltrim(std::string &str) { + if(str.empty()) + return; + size_t n = str.find_first_not_of(IniFile::STR_WS); + if(std::string::npos == n) + str.clear(); // Only whitespace + else + str.erase(0, n); + } + + static void trim(std::string &str) { + rtrim(str); + ltrim(str); + } + + // Trim on a copy and return the trimmed copy + static std::string rtrimcpy(const std::string &str) { + std::string cpy = str; + rtrim(cpy); + return cpy; + } + + static std::string ltrimcpy(const std::string &str) { + std::string cpy = str; + ltrim(cpy); + return cpy; + } + + static std::string trimcpy(const std::string &str) { + std::string cpy = str; + rtrim(cpy); + ltrim(cpy); + return cpy; + } + + // White-space characters for argument to find_first_of() and the like. + // Using static constexpr std::string does not seem to work on Debian 11 + // with clang-19 and older than that. Gcc on debian 11 and before doesn't + // support enough C++20 to build LinuxCNC at all. + static constexpr char STR_WS[] =" \t\v\f\r\n"; + + // This isSpace is guaranteed not to depend on locale + static bool isSpace(char c) { + return std::string::npos != (std::string{STR_WS}).find(c); + } +private: + bool Open(const std::string &filePath); + bool Close() { _inifilecontent = nullptr; _filepath.clear(); return true; } + + bool hasOpenError(const std::string &tag, const std::string §ion) const; + std::string sectionFromTag(const IniFileTag *val) const; + + std::optional findSection(const std::string §ion) const; + std::optional findTag(const std::string &tag, const std::string §ion, int num) const; + std::optional> findTags(const std::string &tag, const std::string §ion) const; + + std::optional convertBool(const IniFileTag *val) const; + std::optional convertSInt(const IniFileTag *val) const; + std::optional convertUInt(const IniFileTag *val) const; + std::optional convertReal(const IniFileTag *val) const; + + const IniFileContent *_inifilecontent; + std::string _filepath; +}; + +} // namespace linuxcnc + +#endif +// vim: ts=4 sw=4 diff --git a/src/emc/ini/inijoint.cc b/src/emc/ini/inijoint.cc index 1ba7b367121..f9c01e4a007 100644 --- a/src/emc/ini/inijoint.cc +++ b/src/emc/ini/inijoint.cc @@ -7,276 +7,222 @@ * Author: * License: GPL Version 2 * System: Linux -* -* Copyright (c) 2004-2009 All rights reserved. +* +* Copyright (c) 2004-2009,2026 All rights reserved. ********************************************************************/ -#include -#include // NULL -#include // atol(), _itoa() -#include // strcmp() -#include // isdigit() -#include -#include +#include #include "nml_intf/emc.hh" #include "libnml/rcs/rcs_print.hh" -#include "emcIniFile.hh" -#include "inijoint.hh" // these decls -#include "nml_intf/emcglb.h" // EMC_DEBUG -#include "nml_intf/emccfg.h" // default values for globals +#include "nml_intf/emcglb.h" +#include "nml_intf/emccfg.h" +#include "inifile.hh" #include "inihal.hh" +#include "inijoint.hh" -extern value_inihal_data old_inihal_data; -/* - loadJoint(int joint) - - Loads INI file params for joint, joint = 0, ... - - TYPE type of joint - MAX_VELOCITY max vel for joint - MAX_ACCELERATION max accel for joint - BACKLASH backlash - MIN_LIMIT minimum soft position limit - MAX_LIMIT maximum soft position limit - FERROR maximum following error, scaled to max vel - MIN_FERROR minimum following error - HOME home position (where to go after home) - HOME_FINAL_VEL speed to move from HOME_OFFSET to HOME location (at the end of homing) - HOME_OFFSET home switch/index pulse location - HOME_SEARCH_VEL homing speed, search phase - HOME_LATCH_VEL homing speed, latch phase - HOME_USE_INDEX use index pulse when homing - HOME_IGNORE_LIMITS ignore limit switches when homing - COMP_FILE file of joint compensation points - - calls: +using namespace linuxcnc; - emcJointSetType(int joint, unsigned char jointType); - emcJointSetUnits(int joint, double units); - emcJointSetBacklash(int joint, double backlash); - emcJointSetMinPositionLimit(int joint, double limit); - emcJointSetMaxPositionLimit(int joint, double limit); - emcJointSetFerror(int joint, double ferror); - emcJointSetMinFerror(int joint, double ferror); - emcJointSetHomingParams(int joint, double home, double offset, double home_vel, - double search_vel, double latch_vel, - int use_index, int encoder_does_not_reset, - int ignore_limits, int is_shared, int sequence, int volatile_home)); - emcJointActivate(int joint); - emcJointSetMaxVelocity(int joint, double vel); - emcJointSetMaxAcceleration(int joint, double acc); - emcJointLoadComp(int joint, const char * file, int comp_file_type); - */ +extern value_inihal_data old_inihal_data; -static int loadJoint(int joint, EmcIniFile *jointIniFile) +static void inline print_dbg_config(const std::string &s) { - char jointString[16]; - EmcJointType jointType; - double units; - double backlash; - double offset; - double limit; - double home; - double search_vel; - double latch_vel; - double final_vel; // moving from OFFSET to HOME - bool use_index; - bool encoder_does_not_reset; - bool ignore_limits; - bool is_shared; - int sequence; - int volatile_home; - int locking_indexer; - int absolute_encoder; - int comp_file_type; //type for the compensation file. type==0 means nom, forw, rev. - double maxVelocity; - double maxAcceleration; - double maxJerk; - double ferror; - - // compose string to match, joint = 0 -> JOINT_0, etc. - snprintf(jointString, sizeof(jointString), "JOINT_%d", joint); - - jointIniFile->EnableExceptions(EmcIniFile::ERR_CONVERSION); - - try { - // set joint type - jointType = EMC_LINEAR; // default - jointIniFile->Find(&jointType, "TYPE", jointString); - if (0 != emcJointSetType(joint, jointType)) { - return -1; - } - - // set units - if(jointType == EMC_LINEAR){ - units = emcTrajGetLinearUnits(); - }else{ - units = emcTrajGetAngularUnits(); - } - if (0 != emcJointSetUnits(joint, units)) { - return -1; - } + if (emc_debug & EMC_DEBUG_CONFIG) { + rcs_print_error("%s", (fmt::format("{}: failed\n", s)).c_str()); + } +} - // set backlash - backlash = 0; // default - jointIniFile->Find(&backlash, "BACKLASH", jointString); - if (0 != emcJointSetBacklash(joint, backlash)) { - return -1; - } - old_inihal_data.joint_backlash[joint] = backlash; +static EmcJointType getJointType(const IniFile &ini, const std::string &var, const std::string &sec, EmcJointType def) +{ + static const std::map jointTypeMap = { + {"LINEAR", EMC_LINEAR }, + {"ANGULAR", EMC_ANGULAR} + }; + if(auto c = ini.findMap(jointTypeMap, var, sec)) + return *c; + return def; +} - // set min position limit - limit = -1e99; // default - jointIniFile->Find(&limit, "MIN_LIMIT", jointString); - if (0 != emcJointSetMinPositionLimit(joint, limit)) { - return -1; - } - old_inihal_data.joint_min_limit[joint] = limit; +// +// Load INI file params for joint +// +// Section [JOINT_n] where 'n' is a number in range [0,EMC_MAX_JOINT-1] +// +// [JOINT_n]TYPE Type of joint +// [JOINT_n]BACKLASH Backlash of joint +// [JOINT_n]MIN_LIMIT Minimum soft position limit +// [JOINT_n]MAX_LIMIT Maximum soft position limit +// [JOINT_n]FERROR Maximum following error, scaled to maximum velocity +// [JOINT_n]MIN_FERROR Minimum following error +// [JOINT_n]HOME Home position (where to go after home) +// [JOINT_n]HOME_OFFSET Home switch/index pulse location +// [JOINT_n]HOME_SEARCH_VEL Homing speed, search phase +// [JOINT_n]HOME_LATCH_VEL Homing speed, latch phase +// [JOINT_n]HOME_FINAL_VEL Speed to move from HOME_OFFSET to HOME location (at the end of homing) +// [JOINT_n]HOME_IS_SHARED The home switch input is shared between joints +// [JOINT_n]HOME_USE_INDEX Use index pulse when homing +// [JOINT_n]HOME_INDEX_NO_ENCODER_RESET +// [JOINT_n]HOME_IGNORE_LIMITS Ignore limit switches when homing +// [JOINT_n]VOLATILE_HOME Must re-home after estop or machine off +// [JOINT_n]LOCKING_INDEXER +// [JOINT_n]HOME_ABSOLUTE_ENCODER +// [JOINT_n]HOME_SEQUENCE +// [JOINT_n]MAX_VELOCITY Maximum velocity for joint (ds/dt) +// [JOINT_n]MAX_ACCELERATION Maximum acceleration for joint (dv/dt) +// [JOINT_n]MAX_JERK Maximum jerk for joint (da/dt) +// [JOINT_n]COMP_FILE_TYPE Compensation file format [0,1] +// [JOINT_n]COMP_FILE File of joint compensation points +// +static int loadJoint(int joint, const IniFile &ini) +{ + std::string jointSection = fmt::format("JOINT_{}", joint); - // set max position limit - limit = 1e99; // default - jointIniFile->Find(&limit, "MAX_LIMIT", jointString); - if (0 != emcJointSetMaxPositionLimit(joint, limit)) { - return -1; - } - old_inihal_data.joint_max_limit[joint] = limit; + EmcJointType jointType = getJointType(ini, "TYPE", jointSection, EMC_LINEAR); + if (0 != emcJointSetType(joint, jointType)) { + print_dbg_config("emcJointSetType"); + return -1; + } - // set following error limit (at max speed) - ferror = 1; // default - jointIniFile->Find(&ferror, "FERROR", jointString); - if (0 != emcJointSetFerror(joint, ferror)) { - return -1; - } - old_inihal_data.joint_ferror[joint] = ferror; + double units; + switch(jointType) { + case EMC_LINEAR: units = emcTrajGetLinearUnits(); break; + case EMC_ANGULAR: units = emcTrajGetAngularUnits(); break; + default: return -1; + } + if (0 != emcJointSetUnits(joint, units)) { + print_dbg_config("emcJointSetUnits"); + return -1; + } - // do MIN_FERROR, if it's there. If not, use value of maxFerror above - jointIniFile->Find(&ferror, "MIN_FERROR", jointString); - if (0 != emcJointSetMinFerror(joint, ferror)) { - return -1; - } - old_inihal_data.joint_min_ferror[joint] = ferror; + double backlash = ini.findRealV("BACKLASH", jointSection, 0.0); + if (0 != emcJointSetBacklash(joint, backlash)) { + print_dbg_config("emcJointSetBacklash"); + return -1; + } + old_inihal_data.joint_backlash[joint] = backlash; - // set homing paramsters - home = 0; // default - jointIniFile->Find(&home, "HOME", jointString); - old_inihal_data.joint_home[joint] = home; + double limit = ini.findRealV("MIN_LIMIT", jointSection, -1e99); + if (0 != emcJointSetMinPositionLimit(joint, limit)) { + print_dbg_config("emcJointSetMinPositionLimit"); + return -1; + } + old_inihal_data.joint_min_limit[joint] = limit; - offset = 0; // default - jointIniFile->Find(&offset, "HOME_OFFSET", jointString); - old_inihal_data.joint_home_offset[joint] = offset; + limit = ini.findRealV("MAX_LIMIT", jointSection, 1e99); + if (0 != emcJointSetMaxPositionLimit(joint, limit)) { + print_dbg_config("emcJointSetMaxPositionLimit"); + return -1; + } + old_inihal_data.joint_max_limit[joint] = limit; - search_vel = 0; // default - jointIniFile->Find(&search_vel, "HOME_SEARCH_VEL", jointString); - latch_vel = 0; // default - jointIniFile->Find(&latch_vel, "HOME_LATCH_VEL", jointString); - final_vel = -1; // default (rapid) - jointIniFile->Find(&final_vel, "HOME_FINAL_VEL", jointString); - is_shared = false; // default - jointIniFile->Find(&is_shared, "HOME_IS_SHARED", jointString); - use_index = false; // default - jointIniFile->Find(&use_index, "HOME_USE_INDEX", jointString); - encoder_does_not_reset = false; // default - jointIniFile->Find(&encoder_does_not_reset, "HOME_INDEX_NO_ENCODER_RESET", jointString); - ignore_limits = false; // default - jointIniFile->Find(&ignore_limits, "HOME_IGNORE_LIMITS", jointString); + double ferror = ini.findRealV("FERROR", jointSection, 1.0); + if (0 != emcJointSetFerror(joint, ferror)) { + print_dbg_config("emcJointSetFerror"); + return -1; + } + old_inihal_data.joint_ferror[joint] = ferror; - sequence = 999;// default: use unrealizable and positive sequence no. - // so that joints with unspecified HOME_SEQUENCE= - // will not be homed in home-all - jointIniFile->Find(&sequence, "HOME_SEQUENCE", jointString); - old_inihal_data.joint_home_sequence[joint] = sequence; + // MIN_FERROR - uses default from FERROR above + ferror = ini.findRealV("MIN_FERROR", jointSection, ferror); + if (0 != emcJointSetMinFerror(joint, ferror)) { + print_dbg_config("emcJointSetMinFerror"); + return -1; + } + old_inihal_data.joint_min_ferror[joint] = ferror; + + // Homing parameters + double home = ini.findRealV("HOME", jointSection, 0.0); + old_inihal_data.joint_home[joint] = home; + double offset = ini.findRealV("HOME_OFFSET", jointSection, 0.0); + old_inihal_data.joint_home_offset[joint] = offset; + + double search_vel = ini.findRealV("HOME_SEARCH_VEL", jointSection, 0.0); + double latch_vel = ini.findRealV("HOME_LATCH_VEL", jointSection, 0.0); + double final_vel = ini.findRealV("HOME_FINAL_VEL", jointSection, -1.0); + bool is_shared = ini.findBoolV("HOME_IS_SHARED", jointSection, false); + bool use_index = ini.findBoolV("HOME_USE_INDEX", jointSection, false); + bool encoder_reset = ini.findBoolV("HOME_INDEX_NO_ENCODER_RESET", jointSection, false); + bool ignore_limits = ini.findBoolV("HOME_IGNORE_LIMITS", jointSection, false); + bool volatile_home = ini.findBoolV("VOLATILE_HOME", jointSection, false); + bool locking_idxer = ini.findBoolV("LOCKING_INDEXER", jointSection, false); + int abs_encoder = ini.findIntV("HOME_ABSOLUTE_ENCODER", jointSection, 0, 0, 2); + + // Sequence defaults to an unrealizable and positive sequence so that + // joints with unspecified HOME_SEQUENCE= will not be homed in home-all + int sequence = ini.findIntV("HOME_SEQUENCE", jointSection, 999); + old_inihal_data.joint_home_sequence[joint] = sequence; + + if (0 != emcJointSetHomingParams(joint, home, offset, + final_vel, search_vel, latch_vel, + use_index, + encoder_reset, + ignore_limits, + is_shared, + sequence, + volatile_home, + locking_idxer, + abs_encoder)) { + print_dbg_config("emcJointSetHomingParams"); + return -1; + } - volatile_home = 0; // default - jointIniFile->Find(&volatile_home, "VOLATILE_HOME", jointString); - locking_indexer = false; - jointIniFile->Find(&locking_indexer, "LOCKING_INDEXER", jointString); - absolute_encoder = false; - jointIniFile->Find(&absolute_encoder, "HOME_ABSOLUTE_ENCODER", jointString); - // issue NML message to set all params - if (0 != emcJointSetHomingParams(joint, home, offset - ,final_vel, search_vel, latch_vel - ,(int)use_index - ,(int)encoder_does_not_reset - ,(int)ignore_limits - ,(int)is_shared - ,sequence - ,volatile_home - ,locking_indexer - ,absolute_encoder - )) { - return -1; - } + // Velocity, acceleration and jerk + double maxVelocity = ini.findRealV("MAX_VELOCITY", jointSection, DEFAULT_JOINT_MAX_VELOCITY); + if (0 != emcJointSetMaxVelocity(joint, maxVelocity)) { + print_dbg_config("emcJointSetMaxVelocity"); + return -1; + } + old_inihal_data.joint_max_velocity[joint] = maxVelocity; - // set maximum velocity - maxVelocity = DEFAULT_JOINT_MAX_VELOCITY; - jointIniFile->Find(&maxVelocity, "MAX_VELOCITY", jointString); - if (0 != emcJointSetMaxVelocity(joint, maxVelocity)) { - return -1; - } - old_inihal_data.joint_max_velocity[joint] = maxVelocity; + double maxAcceleration = ini.findRealV("MAX_ACCELERATION", jointSection, DEFAULT_JOINT_MAX_ACCELERATION); + if (0 != emcJointSetMaxAcceleration(joint, maxAcceleration)) { + print_dbg_config("emcJointSetMaxAcceleration"); + return -1; + } + old_inihal_data.joint_max_acceleration[joint] = maxAcceleration; - maxAcceleration = DEFAULT_JOINT_MAX_ACCELERATION; - jointIniFile->Find(&maxAcceleration, "MAX_ACCELERATION", jointString); - if (0 != emcJointSetMaxAcceleration(joint, maxAcceleration)) { - return -1; - } - old_inihal_data.joint_max_acceleration[joint] = maxAcceleration; + double maxJerk = ini.findRealV("MAX_JERK", jointSection, DEFAULT_JOINT_MAX_JERK); + if (0 != emcJointSetMaxJerk(joint, maxJerk)) { + print_dbg_config("emcJointSetMaxJerk"); + return -1; + } + old_inihal_data.joint_jerk[joint] = maxJerk; - maxJerk = DEFAULT_JOINT_MAX_JERK; - jointIniFile->Find(&maxJerk, "MAX_JERK", jointString); - if (0 != emcJointSetMaxJerk(joint, maxJerk)) { + // Compensation file (backlash alternative) + int comp_file_type = ini.findIntV("COMP_FILE_TYPE", jointSection, 0, 0, 1); + if(auto comp_file = ini.findString("COMP_FILE", jointSection)) { + if (0 != emcJointLoadComp(joint, comp_file->c_str(), comp_file_type)) { + print_dbg_config("emcJointLoadComp"); return -1; } - old_inihal_data.joint_jerk[joint] = maxJerk; - - comp_file_type = 0; // default - jointIniFile->Find(&comp_file_type, "COMP_FILE_TYPE", jointString); - auto comp_file = jointIniFile->Find("COMP_FILE", jointString); - if (comp_file) { - if (0 != emcJointLoadComp(joint, comp_file->c_str(), comp_file_type)) { - return -1; - } - } } - catch (EmcIniFile::Exception &e) { - e.Print(); - return -1; - } - - // lastly, activate joint. Do this last so that the motion controller - // won't flag errors midway during configuration + // Activate joint. + // Doing this last should prevent the motion controller to flag errors + // during configuration if (0 != emcJointActivate(joint)) { + print_dbg_config("emcJointActivate"); return -1; } return 0; } -/* - iniJoint(int joint, const char *filename) - - Loads INI file parameters for specified joint - - Looks for [KINS]JOINTS for how many to do, up to EMC_JOINT_MAX. - */ +// +// iniJoint(int joint, const char *filename) +// Loads INI file parameters for specified joint +// int iniJoint(int joint, const char *filename) { - EmcIniFile jointIniFile(EmcIniFile::ERR_TAG_NOT_FOUND | - EmcIniFile::ERR_SECTION_NOT_FOUND | - EmcIniFile::ERR_CONVERSION); - - if (jointIniFile.Open(filename) == false) { - return -1; + if (joint < 0 || joint >= EMCMOT_MAX_JOINTS) { + rcs_print_error("iniJoint: Invalid joint '%d'", joint); + return -1; } - // load its values - if (0 != loadJoint(joint, &jointIniFile)) { + IniFile ini(filename); + if (!ini) return -1; - } - return 0; + return loadJoint(joint, ini); } - diff --git a/src/emc/ini/inispindle.cc b/src/emc/ini/inispindle.cc index a0e7274caee..7438ae02bcd 100644 --- a/src/emc/ini/inispindle.cc +++ b/src/emc/ini/inispindle.cc @@ -5,124 +5,111 @@ * Derived from a work by Fred Proctor & Will Shackleford * * Author: Andy Pugh +* Author: B.Stultiens * License: GPL Version 2+ * System: Linux * -* Copyright (c) 2021 All rights reserved. +* Copyright (c) 2021,2026 All rights reserved. * * Last change: created 30/12/21 ********************************************************************/ -#include -#include // NULL -#include // atol(), _itoa() -#include // strcmp() -#include // isdigit() -#include -#include +#include #include "nml_intf/emc.hh" #include "libnml/rcs/rcs_print.hh" -#include "emcIniFile.hh" -#include "inispindle.hh" // these decls -#include "nml_intf/emcglb.h" // EMC_DEBUG -#include "nml_intf/emccfg.h" // default values for globals +#include "nml_intf/emcglb.h" +#include "nml_intf/emccfg.h" +#include "inifile.hh" #include "inihal.hh" +#include "inispindle.hh" + +using namespace linuxcnc; extern value_inihal_data old_inihal_data; -/* - loadSpindls(int spindle) +// +// loadSpindle(int spindle) +// +// Loads INI file params for the specified spindle spindle max and min +// velocities. +// The spindle argument is checked against the [TRAJ]SPINDLES setting and must +// be within bounds (otherwise it defaults to 1). +// If the section [SPINDLE_n], with 'n' the spindle being processed, then it +// will be configured using defaults as set in here. +// +// [TRAJ]SPINDLES Number of spindles in system (defaults to 1) +// [SPINDLE_n]MAX_FORWARD_VELOCITY +// [SPINDLE_n]MIN_FORWARD_VELOCITY +// [SPINDLE_n]MIN_REVERSE_VELOCITY +// [SPINDLE_n]MAX_REVERSE_VELOCITY +// [SPINDLE_n]HOME_SEQUENCE +// [SPINDLE_n]HOME_SEARCH_VELOCITY +// -- disabled -- [SPINDLE_n]HOME set home angle - I believe this is a bad idea - andypugh 30/12/21 +// [SPINDLE_n]INCREMENT +// +static int loadSpindle(int spindle, const IniFile &ini) +{ + std::string spindleSection = fmt::format("SPINDLE_{}", spindle); - Loads INI file params for the specified spindle - spindle max and min velocities - */ + int num_spindles = ini.findIntV("SPINDLES", "TRAJ", 1, 1, EMCMOT_MAX_SPINDLES-1); + if (spindle >= num_spindles) { // Cannot configure spindles not present + rcs_print_error("loadSpindle: spindle %d >= ini [SPINDLES]TRAJ %d\n", spindle, num_spindles); + return -1; + } -static int loadSpindle(int spindle, EmcIniFile *spindleIniFile) -{ - int num_spindles = 1; - char spindleString[11]; double fastest_pos = 1e99; double slowest_neg = 0; double slowest_pos = 0; double fastest_neg = -1e99; - int home_sequence = 0; - double search_vel = 0; - double home_angle = 0; - double increment = 100; - double limit; - - spindleIniFile->EnableExceptions(EmcIniFile::ERR_CONVERSION); - - if (spindleIniFile->Find(&num_spindles, "SPINDLES", "TRAJ") < 0){ - num_spindles = 1; } - if (spindle > num_spindles) return -1; - - snprintf(spindleString, sizeof(spindleString), "SPINDLE_%i", spindle); // set max positive speed limit - if (spindleIniFile->Find(&limit, "MAX_FORWARD_VELOCITY", spindleString) == 0){ - fastest_pos = limit; - fastest_neg = -1.0 * limit; + if (auto val = ini.findReal("MAX_FORWARD_VELOCITY", spindleSection)) { + fastest_pos = *val; + fastest_neg = -(*val); } // set min positive speed limit - if (spindleIniFile->Find(&limit, "MIN_FORWARD_VELOCITY", spindleString) == 0){ - slowest_pos = limit; - slowest_neg = -1.0 * limit; + if (auto val = ini.findReal("MIN_FORWARD_VELOCITY", spindleSection)) { + slowest_pos = *val; + slowest_neg = -(*val); } + // set min negative speed limit - if (spindleIniFile->Find(&limit, "MIN_REVERSE_VELOCITY", spindleString) == 0){ - slowest_neg = -1.0 * fabs(limit); + if (auto val = ini.findReal("MIN_REVERSE_VELOCITY", spindleSection)) { + slowest_neg = -fabs(*val); } // set max negative speed limit - if (spindleIniFile->Find(&limit, "MAX_REVERSE_VELOCITY", spindleString) == 0){ - fastest_neg = -1.0 * fabs(limit); - } - // set home sequence - if (spindleIniFile->Find(&limit, "HOME_SEQUENCE", spindleString) == 0){ - home_sequence = (int)limit; - } - // set home velocity - if (spindleIniFile->Find(&limit, "HOME_SEARCH_VELOCITY", spindleString) == 0){ - search_vel = (int)limit; - } - /* set home angle - I believe this is a bad idea - andypugh 30/12/21 - if (spindleIniFile->Find(&limit, "HOME", spindleString) >= 0){ - home_angle = (int)limit; - }*/ - home_angle = 0; - // set spindle increment - if (spindleIniFile->Find(&limit, "INCREMENT", spindleString) == 0){ - increment = limit; + if (auto val = ini.findReal("MAX_REVERSE_VELOCITY", spindleSection)) { + fastest_neg = -fabs(*val); } - if (0 != emcSpindleSetParams(spindle, fastest_pos, slowest_pos, slowest_neg, - fastest_neg, search_vel, home_angle, home_sequence, increment)) { + int home_sequence = ini.findIntV("HOME_SEQUENCE", spindleSection, 0); + double search_vel = ini.findRealV("HOME_SEARCH_VELOCITY", spindleSection, 0.0); + // set home angle - I believe this is a bad idea - andypugh 30/12/21 + //double home_angle = ini.findRealV("HOME", spindleSection, 0.0); + double home_angle = 0.0; + double increment = ini.findRealV("INCREMENT", spindleSection, 100.0); + + if (0 != emcSpindleSetParams(spindle, fastest_pos, slowest_pos, + slowest_neg, fastest_neg, search_vel, + home_angle, home_sequence, increment)) { + rcs_print_error("emcSpindleSetParams: failed\n"); return -1; } return 0; } -/* - iniAxis(int axis, const char *filename) - - Loads INI file parameters for specified axis, [0 .. AXES - 1] - - */ int iniSpindle(int spindle, const char *filename) { - EmcIniFile spindleIniFile(EmcIniFile::ERR_TAG_NOT_FOUND | - EmcIniFile::ERR_SECTION_NOT_FOUND | - EmcIniFile::ERR_CONVERSION); - - if (spindleIniFile.Open(filename) == false) { - return -1; + if (spindle < 0 || spindle >= EMCMOT_MAX_SPINDLES) { + rcs_print_error("iniJoint: Invalid spindle '%d'\n", spindle); + return -1; } - // load its values - if (0 != loadSpindle(spindle, &spindleIniFile)) { + IniFile ini(filename); + if (!ini) return -1; - } - return 0; + + return loadSpindle(spindle, ini); } diff --git a/src/emc/ini/initraj.cc b/src/emc/ini/initraj.cc index dd00a974207..671b33b362c 100644 --- a/src/emc/ini/initraj.cc +++ b/src/emc/ini/initraj.cc @@ -7,369 +7,268 @@ * Author: * License: GPL Version 2 * System: Linux -* -* Copyright (c) 2004 All rights reserved. +* +* Copyright (c) 2026 All rights reserved. ********************************************************************/ -#include // NULL -#include // atol() -#include // strlen() -#include // isspace() +#include +#include #include "nml_intf/emc.hh" -#include // EmcPose -#include // PM_POSE, PM_RPY +#include #include "libnml/rcs/rcs_print.hh" -#include "emcIniFile.hh" -#include "initraj.hh" // these decls -#include "nml_intf/emcglb.h" /*! \todo TRAVERSE_RATE (FIXME) */ -#include "inihal.hh" -#include - -extern value_inihal_data old_inihal_data; +#include "nml_intf/emcglb.h" +#include "inifile.hh" -/* - loadKins() +#include "inihal.hh" +#include "initraj.hh" - JOINTS number of joints (DOF) in system +using namespace linuxcnc; - calls: +extern value_inihal_data old_inihal_data; - emcTrajSetJoints(int joints); -*/ +static void inline print_dbg_config(const std::string &s) +{ + if (emc_debug & EMC_DEBUG_CONFIG) { + rcs_print_error("%s", (fmt::format("{}: failed\n", s)).c_str()); + } +} -static int loadKins(EmcIniFile *trajInifile) +static double findLinearUnits(const IniFile &ini, const std::string &var, const std::string &sec, double def) { - trajInifile->EnableExceptions(EmcIniFile::ERR_CONVERSION); + // The const map holds pairs for linear units which are valid under the + // [TRAJ] section. These are of the form {"name", value}. + // If the name "name" is encountered in the INI, the value will be used. + static const std::map linearUnitsMap = { + { "mm", 1.0 }, + { "metric", 1.0 }, + { "in", 1/25.4 }, + { "inch", 1/25.4 }, + { "imperial", 1/25.4 }, + }; + + if(auto c = ini.findMap(linearUnitsMap, var, sec)) + return *c; + return def; +} - try { - int joints = 0; - trajInifile->Find(&joints, "JOINTS", "KINS"); +static double findAngularUnits(const IniFile &ini, const std::string &var, const std::string &sec, double def) +{ + // The const map holds pairs for angular units which are valid under + // the [TRAJ] section. These are of the form {"name", value}. + // If the name "name" is encountered in the INI, the value will be used. + static const std::map angularUnitsMap = { + { "deg", 1.0 }, + { "degree", 1.0 }, + { "grad", 0.9 }, + { "gon", 0.9 }, + { "rad", M_PI / 180.0 }, + { "radian", M_PI / 180.0 }, + }; + + if(auto c = ini.findMap(angularUnitsMap, var, sec)) + return *c; + return def; +} - if (0 != emcTrajSetJoints(joints)) { - return -1; - } - } - - catch (EmcIniFile::Exception &e) { - e.Print(); +// +// loadKins() +// +// JOINTS number of joints (DOF) in system +// +static int loadKins(const IniFile &ini) +{ + int joints = ini.findIntV("JOINTS", "KINS", 0); + if (0 != emcTrajSetJoints(joints)) { + print_dbg_config("emcTrajSetJoints"); return -1; } - return 0; } -/* - loadTraj() - - Loads INI file params for traj - - COORDINATES axes in system - LINEAR_UNITS units per mm - ANGULAR_UNITS units per degree - DEFAULT_LINEAR_VELOCITY default linear velocity - MAX_LINEAR_VELOCITY max linear velocity - DEFAULT_LINEAR_ACCELERATION default linear acceleration - MAX_LINEAR_ACCELERATION max linear acceleration +// +// loadTraj() +// +// Load INI file params for traj +// Note: MAX_FEED_OVERRIDE comes from the [DISPLAY] section +// +// [TRAJ]SPINDLES Number of spindles configured +// [TRAJ]COORDINATES Axes configured in system (sets axis mask) +// [TRAJ]LINEAR_UNITS Units per mm +// [TRAJ]ANGULAR_UNITS Units per degree +// [TRAJ]DEFAULT_LINEAR_VELOCITY Default linear velocity (ds/dt) +// [TRAJ]MAX_LINEAR_VELOCITY Maximum linear velocity (ds/dt) +// [TRAJ]DEFAULT_LINEAR_ACCELERATION Default linear acceleration (dv/dt) +// [TRAJ]MAX_LINEAR_ACCELERATION Maximum linear acceleration (dv/dt) +// [TRAJ]MAX_LINEAR_JERK Maximum linear jerk (da/dt) +// [TRAJ]PLANNER_TYPE Planner type (0=standard, 1=S-curve) +// [TRAJ]ARC_BLEND_ENABLE S-curve planner settings +// [TRAJ]ARC_BLEND_FALLBACK_ENABLE ... +// [TRAJ]ARC_BLEND_OPTIMIZATION_DEPTH ... +// [TRAJ]ARC_BLEND_GAP_CYCLES ... +// [TRAJ]ARC_BLEND_RAMP_FREQ ... +// [TRAJ]ARC_BLEND_KINK_RATIO ... +// [DISPLAY]MAX_FEED_OVERRIDE +// [TRAJ]NO_PROBE_JOG_ERROR +// [TRAJ]NO_PROBE_HOME_ERROR +// [TRAJ]HOME Home position (pose) one coordinate for each axis +// +static int loadTraj(const IniFile &ini) +{ + int spindles = ini.findIntV("SPINDLES", "TRAJ", 1); + if (0 != emcTrajSetSpindles(spindles)) { + print_dbg_config("emcTrajSetSpindles"); + return -1; + } - calls: + int axismask = 0; + if (auto coord = ini.findString("COORDINATES", "TRAJ")) { + static const std::string axes{"XYZABCUVW"}; + for (auto c : *coord) { + size_t n = axes.find_first_of(std::toupper(c & 0xff)); + if (n != std::string::npos) + axismask |= 1 << n; + } + } else { + axismask = (1<<0) | (1<<1) | (1<<2); // X, Y and Z machine + } + if (0 != emcTrajSetAxes(axismask)) { + print_dbg_config("emcTrajSetAxes"); + return -1; + } - emcTrajSetAxes(int axes, int axismask); - emcTrajSetUnits(double linearUnits, double angularUnits); - emcTrajSetVelocity(double vel, double ini_maxvel); - emcTrajSetAcceleration(double acc); - emcTrajSetMaxVelocity(double vel); - emcTrajSetMaxAcceleration(double acc); - */ -static int loadTraj(EmcIniFile *trajInifile) -{ - EmcLinearUnits linearUnits; - EmcAngularUnits angularUnits; - double vel; - double acc; - double jerk; - int planner_type; - - trajInifile->EnableExceptions(EmcIniFile::ERR_CONVERSION); - - try{ - int spindles = 1; - trajInifile->Find(&spindles, "SPINDLES", "TRAJ"); - if (0 != emcTrajSetSpindles(spindles)) { - return -1; - } + double linearUnits = findLinearUnits(ini, "LINEAR_UNITS", "TRAJ", 0.0); + double angularUnits = findAngularUnits(ini, "ANGULAR_UNITS", "TRAJ", 0.0); + if (0 != emcTrajSetUnits(linearUnits, angularUnits)) { + rcs_print("emcTrajSetUnits failed to set [TRAJ]LINEAR_UNITS or [TRAJ]ANGULAR_UNITS\n"); + return -1; } - catch (EmcIniFile::Exception &e) { - e.Print(); + double velocity = ini.findRealV("DEFAULT_LINEAR_VELOCITY", "TRAJ", 1.0); + if (0 != emcTrajSetVelocity(0.0, velocity)) { // Default velocity=0.0 on startup + print_dbg_config("emcTrajSetVelocity"); return -1; } + old_inihal_data.traj_default_velocity = velocity; - try{ - int axismask = 0; - auto coord = trajInifile->Find("COORDINATES", "TRAJ"); - if(coord) { - if(coord->find_first_of("xX") != std::string::npos) { - axismask |= 1; - } - if(coord->find_first_of("yY") != std::string::npos) { - axismask |= 2; - } - if(coord->find_first_of("zZ") != std::string::npos) { - axismask |= 4; - } - if(coord->find_first_of("aA") != std::string::npos) { - axismask |= 8; - } - if(coord->find_first_of("bB") != std::string::npos) { - axismask |= 16; - } - if(coord->find_first_of("cC") != std::string::npos) { - axismask |= 32; - } - if(coord->find_first_of("uU") != std::string::npos) { - axismask |= 64; - } - if(coord->find_first_of("vV") != std::string::npos) { - axismask |= 128; - } - if(coord->find_first_of("wW") != std::string::npos) { - axismask |= 256; - } - } else { - axismask = 1 | 2 | 4; // default: XYZ machine - } - if (0 != emcTrajSetAxes(axismask)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetAxes\n"); - } - return -1; - } - - linearUnits = 0; - trajInifile->FindLinearUnits(&linearUnits, "LINEAR_UNITS", "TRAJ"); - angularUnits = 0; - trajInifile->FindAngularUnits(&angularUnits, "ANGULAR_UNITS", "TRAJ"); - if (0 != emcTrajSetUnits(linearUnits, angularUnits)) { - rcs_print("emcTrajSetUnits failed to set " - "[TRAJ]LINEAR_UNITS or [TRAJ]ANGULAR_UNITS\n"); - return -1; - } - vel = 1.0; - trajInifile->Find(&vel, "DEFAULT_LINEAR_VELOCITY", "TRAJ"); - old_inihal_data.traj_default_velocity = vel; + velocity = ini.findRealV("MAX_LINEAR_VELOCITY", "TRAJ", 1e99); + if (0 != emcTrajSetMaxVelocity(velocity)) { + print_dbg_config("emcTrajSetMaxVelocity"); + return -1; + } + old_inihal_data.traj_max_velocity = velocity; - // and set dynamic value - if (0 != emcTrajSetVelocity(0, vel)) { //default velocity on startup 0 - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetVelocity\n"); - } - return -1; - } - vel = 1e99; // by default, use AXIS limit - trajInifile->Find(&vel, "MAX_LINEAR_VELOCITY", "TRAJ"); - // XXX CJR merge question: Set something in TrajConfig here? - old_inihal_data.traj_max_velocity = vel; + double accel = ini.findRealV("DEFAULT_LINEAR_ACCELERATION", "TRAJ", 1e99); + if (0 != emcTrajSetAcceleration(accel)) { + print_dbg_config("emcTrajSetAcceleration"); + return -1; + } + old_inihal_data.traj_default_acceleration = accel; - // and set dynamic value - if (0 != emcTrajSetMaxVelocity(vel)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetMaxVelocity\n"); - } - return -1; - } + accel = ini.findRealV("MAX_LINEAR_ACCELERATION", "TRAJ", 1e99); + if (0 != emcTrajSetMaxAcceleration(accel)) { + print_dbg_config("emcTrajSetMaxAcceleration"); + return -1; + } + old_inihal_data.traj_max_acceleration = accel; - acc = 1e99; // let the axis values apply - trajInifile->Find(&acc, "DEFAULT_LINEAR_ACCELERATION", "TRAJ"); - if (0 != emcTrajSetAcceleration(acc)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetAcceleration\n"); - } - return -1; - } - old_inihal_data.traj_default_acceleration = acc; + // Set max jerk (default to 1e9 if not specified in INI) + double jerk = ini.findRealV("MAX_LINEAR_JERK", "TRAJ", 1e9); + // Set both current and max jerk + if (0 != emcTrajSetMaxJerk(jerk)) { + print_dbg_config("emcTrajSetMaxJerk"); + return -1; + } + old_inihal_data.traj_max_jerk = jerk; + if (0 != emcTrajSetJerk(jerk)) { + print_dbg_config("emcTrajSetJerk"); + return -1; + } - acc = 1e99; // let the axis values apply - trajInifile->Find(&acc, "MAX_LINEAR_ACCELERATION", "TRAJ"); - if (0 != emcTrajSetMaxAcceleration(acc)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetMaxAcceleration\n"); - } - return -1; - } - old_inihal_data.traj_max_acceleration = acc; - - // Set max jerk (default to 1e9 if not specified in INI) - jerk = 1e9; - trajInifile->Find(&jerk, "MAX_LINEAR_JERK", "TRAJ"); - if (0 != emcTrajSetMaxJerk(jerk)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetMaxJerk\n"); - } - return -1; - } - old_inihal_data.traj_max_jerk = jerk; - // Also set current jerk to max_jerk - if (0 != emcTrajSetJerk(jerk)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetJerk\n"); - } - return -1; - } - planner_type = 0; // Default: 0 = trapezoidal, 1 = S-curve - trajInifile->Find(&planner_type, "PLANNER_TYPE", "TRAJ"); - // Only 0 and 1 are supported, set to 0 if invalid - // Also force planner type 0 if max_jerk < 1 (S-curve needs valid jerk) - if (planner_type != 0 && planner_type != 1) { - planner_type = 0; - } - if (planner_type == 1 && jerk < 1.0) { - planner_type = 0; - } - if (0 != emcTrajPlannerType(planner_type)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajPlannerType\n"); - } - return -1; - } - old_inihal_data.traj_planner_type = planner_type; - - int arcBlendEnable = 1; - int arcBlendFallbackEnable = 0; - int arcBlendOptDepth = 50; - int arcBlendGapCycles = 4; - double arcBlendRampFreq = 100.0; - double arcBlendTangentKinkRatio = 0.1; - - trajInifile->Find(&arcBlendEnable, "ARC_BLEND_ENABLE", "TRAJ"); - trajInifile->Find(&arcBlendFallbackEnable, "ARC_BLEND_FALLBACK_ENABLE", "TRAJ"); - trajInifile->Find(&arcBlendOptDepth, "ARC_BLEND_OPTIMIZATION_DEPTH", "TRAJ"); - trajInifile->Find(&arcBlendGapCycles, "ARC_BLEND_GAP_CYCLES", "TRAJ"); - trajInifile->Find(&arcBlendRampFreq, "ARC_BLEND_RAMP_FREQ", "TRAJ"); - trajInifile->Find(&arcBlendTangentKinkRatio, "ARC_BLEND_KINK_RATIO", "TRAJ"); - - if (0 != emcSetupArcBlends(arcBlendEnable, arcBlendFallbackEnable, - arcBlendOptDepth, arcBlendGapCycles, arcBlendRampFreq, arcBlendTangentKinkRatio)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcSetupArcBlends\n"); - } - return -1; - } - - old_inihal_data.traj_arc_blend_enable = arcBlendEnable; - old_inihal_data.traj_arc_blend_fallback_enable = arcBlendFallbackEnable; - old_inihal_data.traj_arc_blend_optimization_depth = arcBlendOptDepth; - old_inihal_data.traj_arc_blend_gap_cycles = arcBlendGapCycles; - old_inihal_data.traj_arc_blend_ramp_freq = arcBlendRampFreq; - old_inihal_data.traj_arc_blend_tangent_kink_ratio = arcBlendTangentKinkRatio; - //TODO update inihal - - double maxFeedScale = 1.0; - trajInifile->Find(&maxFeedScale, "MAX_FEED_OVERRIDE", "DISPLAY"); - - if (0 != emcSetMaxFeedOverride(maxFeedScale)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcSetMaxFeedOverride\n"); - } - return -1; - } - - int j_inhibit = 0; - int h_inhibit = 0; - trajInifile->Find(&j_inhibit, "NO_PROBE_JOG_ERROR", "TRAJ"); - trajInifile->Find(&h_inhibit, "NO_PROBE_HOME_ERROR", "TRAJ"); - if (0 != emcSetProbeErrorInhibit(j_inhibit, h_inhibit)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcSetProbeErrorInhibit\n"); - } - return -1; - } + // Planner: 0 = trapezoidal, 1 = S-curve + // Default = 0 + int planner_type = ini.findIntV("PLANNER_TYPE", "TRAJ", 0, 0, 1); + // Also force planner type 0 if max_jerk < 1 (S-curve needs valid jerk) + if (planner_type == 1 && jerk < 1.0) { + // FIXME: Should write a warning message to the user + planner_type = 0; + } + if (0 != emcTrajPlannerType(planner_type)) { + print_dbg_config("emcTrajPlannerType"); + return -1; + } + old_inihal_data.traj_planner_type = planner_type; + + int arcBlendEnable = ini.findBoolV("ARC_BLEND_ENABLE", "TRAJ", true); + int arcBlendFallbackEnable = ini.findBoolV("ARC_BLEND_FALLBACK_ENABLE", "TRAJ", false); + int arcBlendOptDepth = ini.findIntV("ARC_BLEND_OPTIMIZATION_DEPTH", "TRAJ", 50); + int arcBlendGapCycles = ini.findIntV("ARC_BLEND_GAP_CYCLES", "TRAJ", 4); + double arcBlendRampFreq = ini.findRealV("ARC_BLEND_RAMP_FREQ", "TRAJ", 100.0); + double arcBlendTangentKinkRatio = ini.findRealV("ARC_BLEND_KINK_RATIO", "TRAJ", 0.1); + if (0 != emcSetupArcBlends(arcBlendEnable, arcBlendFallbackEnable, + arcBlendOptDepth, arcBlendGapCycles, arcBlendRampFreq, arcBlendTangentKinkRatio)) { + print_dbg_config("emcSetupArcBlends"); + return -1; + } + old_inihal_data.traj_arc_blend_enable = arcBlendEnable; + old_inihal_data.traj_arc_blend_fallback_enable = arcBlendFallbackEnable; + old_inihal_data.traj_arc_blend_optimization_depth = arcBlendOptDepth; + old_inihal_data.traj_arc_blend_gap_cycles = arcBlendGapCycles; + old_inihal_data.traj_arc_blend_ramp_freq = arcBlendRampFreq; + old_inihal_data.traj_arc_blend_tangent_kink_ratio = arcBlendTangentKinkRatio; + //TODO update inihal + + double maxFeedScale = ini.findRealV("MAX_FEED_OVERRIDE", "DISPLAY", 1.0); + if (0 != emcSetMaxFeedOverride(maxFeedScale)) { + print_dbg_config("emcSetMaxFeedOverride"); + return -1; } - catch (EmcIniFile::Exception &e) { - e.Print(); + bool j_inhibit = ini.findBoolV("NO_PROBE_JOG_ERROR", "TRAJ", false); + bool h_inhibit = ini.findBoolV("NO_PROBE_HOME_ERROR", "TRAJ", false); + if (0 != emcSetProbeErrorInhibit(j_inhibit, h_inhibit)) { + print_dbg_config("emcSetProbeErrorInhibit"); return -1; } - try{ - unsigned char coordinateMark[6] = { 1, 1, 1, 0, 0, 0 }; - int t; - int len; - char homes[LINELEN]; - char home[LINELEN]; - EmcPose homePose = { {0.0, 0.0, 0.0}, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; - double d; - auto inistring = trajInifile->Find("HOME", "TRAJ"); - if (inistring) { - // [TRAJ]HOME is important for genhexkins.c kinetmaticsForward() - // and probably other non-identity kins that solve the forward - // kinematics with an iterative algorithm when the homePose - // is not all zeros - - // found it, now interpret it according to coordinateMark[] - rtapi_strxcpy(homes, inistring->c_str()); - len = 0; - for (t = 0; t < 6; t++) { - if (!coordinateMark[t]) { - continue; // position t at index of next non-zero mark - } - // there is a mark, so read the string for a value - if (1 == sscanf(&homes[len], "%254s", home) && - 1 == sscanf(home, "%lf", &d)) { - // got an entry, index into coordinateMark[] is 't' - if (t == 0) - homePose.tran.x = d; - else if (t == 1) - homePose.tran.y = d; - else if (t == 2) - homePose.tran.z = d; - else if (t == 3) - homePose.a = d; - else if (t == 4) - homePose.b = d; - else if (t == 5) - homePose.c = d; -/* - * The following have no effect. The loop only counts [0..5]. - else if (t == 6) - homePose.u = d; - else if (t == 7) - homePose.v = d; - else - homePose.w = d; -*/ - - // position string ptr past this value - len += strlen(home); - // and at start of next value - while ((len < LINELEN) && (homes[len] == ' ' || homes[len] == '\t')) { - len++; - } - if (len >= LINELEN) { - break; // out of for loop - } - } else { - // badly formatted entry - rcs_print("invalid INI file value for [TRAJ] HOME: %s\n", - inistring->c_str()); - return -1; - } - } // end of for-loop on coordinateMark[] + + if (auto inistring = ini.findString("HOME", "TRAJ")) { + std::vector toks = IniFile::split(" \t", *inistring); + // NOTE: originally, this code would only set axes X, Y and Z and + // ignore everything else. Now all axes are set if provided in the + // [TRAJ]HOME position. + EmcPose homePose{}; + for (size_t i = 0; i < toks.size() && i <= EMCMOT_MAX_AXIS; i++) { + char *eptr; + errno = 0; + double val = strtod(toks[i].c_str(), &eptr); + if (errno || *eptr || eptr == toks[i].c_str()) { + rcs_print_error("Invalid value '%s' for axis %zu in homePose\n", toks[i].c_str(), i); + return -1; + } + switch(i) { + case 0: homePose.tran.x = val; break; + case 1: homePose.tran.y = val; break; + case 2: homePose.tran.z = val; break; + case 3: homePose.a = val; break; + case 4: homePose.b = val; break; + case 5: homePose.c = val; break; + case 6: homePose.u = val; break; + case 7: homePose.v = val; break; + case 8: homePose.w = val; break; + default: + // Should never trigger because of EMCMOT_MAX_AXIS, but you never know + rcs_print_error("Value for invalid axis number %zu cannot be part of homePose\n", i); + return -1; + } } if (0 != emcTrajSetHome(homePose)) { - if (emc_debug & EMC_DEBUG_CONFIG) { - rcs_print("bad return value from emcTrajSetHome\n"); - } + print_dbg_config("emcTrajSetHome"); return -1; } - } //try - - catch (EmcIniFile::Exception &e) { - e.Print(); - return -1; } return 0; } @@ -381,19 +280,13 @@ static int loadTraj(EmcIniFile *trajInifile) */ int iniTraj(const char *filename) { - EmcIniFile trajInifile; + IniFile ini(filename); + if (!ini) + return -1; - if (trajInifile.Open(filename) == false) { - return -1; - } - // load trajectory values - if (0 != loadKins(&trajInifile)) { - return -1; - } - // load trajectory values - if (0 != loadTraj(&trajInifile)) { + if (loadKins(ini)) { return -1; } - return 0; + return loadTraj(ini); } diff --git a/src/emc/ini/inivalue.cc b/src/emc/ini/inivalue.cc new file mode 100644 index 00000000000..4bd4b9ac1d9 --- /dev/null +++ b/src/emc/ini/inivalue.cc @@ -0,0 +1,476 @@ +// +// inivalue - Ini-file query program +// Copyright (C) 2026 B.Stultiens +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#include +#include + +#include +#include + +#include "inifile.hh" + +using namespace linuxcnc; + +// +// Note: Short options chosen such to prevent overlap with the old inivar +// program's options (-var,-sec, -num, -tildeexpand and -ini). Therefore, no +// short options -v -s -n -t or -i. +// +static const struct option options[] = { + { "var", required_argument, NULL, 'V' }, + { "sec", required_argument, NULL, 'S' }, + { "num", required_argument, NULL, 'N' }, + { "type", required_argument, NULL, 'y' }, + { "minimum", required_argument, NULL, 'm' }, + { "maximum", required_argument, NULL, 'M' }, + { "all", no_argument, NULL, 'a' }, + { "boolnum", no_argument, NULL, 'b' }, + { "boolpy", no_argument, NULL, 'B' }, + { "tildeexpand", no_argument, NULL, 'T' }, + { "quiet", no_argument, NULL, 'q' }, + { "sections", no_argument, NULL, 'e' }, + { "variables", no_argument, NULL, 'o' }, + { "content", no_argument, NULL, 'c' }, + { "prefix", no_argument, NULL, 'p' }, + { "help", no_argument, NULL, 'h' }, + { } +}; +static const char options_str[] = "V:S:N:y:m:M:abBTqeocph"; + +static const char usage_str[] = + "Usage:\n" + " inivalue --var= [--sec=] [more-options...] \n" + " inivalue --sections \n" + "Options:\n" + " -V|--var=str Locate 'str' named variable\n" + " -S|--sec=str Only look in 'str' named section (default: )\n" + " -N|--num=n Select the 'n'th occurrence of the variable (default: 1)\n" + " -a|--all Return all available variable of specified name\n" + " -y|--type=x Set var type to i[nteger], u[nsigned], r[eal], s[tring] or b[oolean] (default: s)\n" + " -m|--minimum=val Set minimum value to test against (types i and u)\n" + " -M|--maximum=val Set maximum value to test against (types i and u)\n" + " -b|--boolnum Print booleans as 1/0 and not as true/false\n" + " -B|--boolpy Print booleans as python compatible capitalized True/False\n" + " -T|--tildeexpand Substitute ~/path with $(HOME)/path on string values\n" + " -q|--quiet Don't print message if variable not found\n" + " -e|--sections Return all section names from the ini-file\n" + " -o|--variables Return all variables names from the ini-file (may be limited by section)\n" + " -c|--content With -o, return the variable names with content (values)\n" + " -p|--prefix With -o, return the variable names with section name prefixed\n" + " -h|--help This message\n" + ; + +static inline void print_err(const std::string &str) +{ + std::cerr << "error: " << str << std::endl; +} + +// Query/conversion type +enum { + TYPE_NONE, + TYPE_STRING, + TYPE_INTEGER, + TYPE_UNSIGNED, + TYPE_REAL, + TYPE_BOOLEAN, +}; + +// Markers for --minimum and --maximum +#define HAVE_MINI 0x01 +#define HAVE_MAXI 0x02 + +// Error exit values +#define EXIT_ENOENT 2 // No entry/variable found +#define EXIT_ERANGE 3 // Scalar out of range + +// +// Helper to get different radix number parsed properly +// +static std::optional convertValue(const std::string &optval) +{ + std::string val = optval; + unsigned pm = 0; // Default no +/- + int base = 10; // Default to decimal + + // String size must be 3 or more for alternate base values. Two for the + // prefix and at least one digit. A leading +/- may also be present. + // We know that the value from the tag has the leading whitespace removed. + if(val.size() > 1 && ('-' == val[0] || '+' == val[0])) { + pm = 1; + } + + // Detect: [+-]?0[xXoObB]. + if(val.size() > pm+2 && '0' == val[pm]) { + // Sets the radix and remove the prefix + switch(val[pm+1]) { + case 'x': case 'X': base = 16; val.erase(pm, 2); break; + case 'o': case 'O': base = 8; val.erase(pm, 2); break; + case 'b': case 'B': base = 2; val.erase(pm, 2); break; + } + } + + char *eptr; + errno = 0; + rtapi_u64 u = strtoull(val.c_str(), &eptr, base); + + if(eptr == val.c_str() || errno != 0) { + print_err(fmt::format("Invalid number converting '{}'", val)); + return std::nullopt; + } + if(*eptr && !std::isspace(*eptr & 0xff)) { + print_err(fmt::format("Trailing character(s) in numeric conversion of '{}')", val)); + return std::nullopt; + } + return u; +} + +static std::optional convertReal(const std::string &optval) +{ + char *eptr; + errno = 0; + // Make sure we always use the C locale for conversion + std::string lcn = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + double r = strtod(optval.c_str(), &eptr); + setlocale(LC_NUMERIC, lcn.c_str()); + + if(eptr == optval.c_str() || errno != 0) { + print_err(fmt::format("Invalid floating point '{}'", optval)); + return std::nullopt; + } + if(*eptr && !std::isspace(*eptr & 0xff)) { + print_err(fmt::format("Trailing character(s) in floating point of '{}')", optval)); + return std::nullopt; + } + return r; +} + +int main(int argc, char * const argv[]) +{ + int lose = 0; + int num = -1; // Detect 'not set on commandline' + std::string variable; + std::string section; + std::string inipath; + bool tildeexp = false; + bool allvars = false; + bool sections = false; + bool variables= false; + bool content = false; + bool prefix = false; + bool quiet = false; + bool boolnum = false; + bool boolpy = false; + int vtype = TYPE_NONE; + int havemm = 0; + rtapi_u64 mini = 0; + rtapi_u64 maxi = UINT64_MAX; + double rmini = -DBL_MAX; + double rmaxi = +DBL_MAX; + + char *eptr; + int optc; + while(-1 != (optc = getopt_long(argc, argv, options_str, options, NULL))) { + switch(optc) { + case 'V': variable = optarg; break; + case 'S': section = optarg; break; + case 'a': allvars = true; break; + case 'q': quiet = true; break; + case 'T': tildeexp = true; break; + case 'e': sections = true; break; + case 'o': variables= true; break; + case 'c': content = true; break; + case 'p': prefix = true; break; + case 'b': boolnum = true; break; + case 'B': boolpy = true; break; + case 'y': + switch(optarg[0]) { + case 'I': case 'i': vtype = TYPE_INTEGER; break; + case 'U': case 'u': vtype = TYPE_UNSIGNED; break; + case 'B': case 'b': vtype = TYPE_BOOLEAN; break; + case 'S': case 's': vtype = TYPE_STRING; break; + case 'R': case 'r': vtype = TYPE_REAL; break; + default: + print_err(fmt::format("Invalid type identifier '{}' accepted={{i,u,b,s,r}}", optarg[0])); + lose++; + break; + } + break; + case 'm': + if(vtype == TYPE_NONE) { + print_err("Must first specify --type before minimum"); + lose++; + } else { + havemm |= HAVE_MINI; + if(vtype == TYPE_INTEGER || vtype == TYPE_UNSIGNED) { + if(auto m = convertValue(optarg)) { + mini = *m; + } else { + lose++; + } + } else if(vtype == TYPE_REAL) { + if(auto m = convertReal(optarg)) { + rmini = *m; + } else { + lose++; + } + } // else ignore string or boolean + } + break; + case 'M': + if(vtype == TYPE_NONE) { + print_err("Must first specify --type before maximum"); + lose++; + } else { + havemm |= HAVE_MAXI; + if(vtype == TYPE_INTEGER || vtype == TYPE_UNSIGNED) { + if(auto m = convertValue(optarg)) { + maxi = *m; + } else { + lose++; + } + } else if(vtype == TYPE_REAL) { + if(auto m = convertReal(optarg)) { + rmaxi = *m; + } else { + lose++; + } + } // else ignore string or boolean + } + break; + case 'N': + errno = 0; + num = strtol(optarg, &eptr, 0); + if(eptr == optarg || errno != 0 || (*eptr && !isspace(*eptr & 0xff)) || num < 1) { + print_err(fmt::format("Invalid number '{}' in --num argument", optarg)); + lose++; + } + break; + case 'h': + std::cout << usage_str; + return EXIT_FAILURE; + default: + lose++; + break; + } + } + + if(optind < argc) { + inipath = argv[optind]; + } else if(!lose) { // Don't bother if we're already doomed + print_err("Missing ini-file"); + lose++; + } + + if(lose) + return EXIT_FAILURE; + + IniFile ini(inipath); + if(!ini) { + // Instantiating should have printed any error message + // print_err(fmt::format("Failed to open '{}'", inipath)); + return EXIT_FAILURE; + } + + // + // Precedence for command-line option actions: + // 1. -e - list all sections + // 2. -o - list all variable names (optional by section) + // 3. -a - list all values for a variable name (optional by section) + // 4. -V - list the value of a variable (with optional range) + // + if(sections) { + // Return all section names + for(auto const § : ini.findSections()) + std::cout << sect << std::endl; + } else if(variables) { + // Return all variables + if(section.empty()) { + // Do it for each section + for(auto const § : ini.findSections()) { + for(auto const &var : ini.findVariables(sect)) { + if(prefix) + std::cout << '[' << sect << ']'; + std::cout << var.first; + if(content) + std::cout << '=' << var.second; + std::cout << std::endl; + } + } + } else { + // Or just the section we were asked to handle + for(auto const &var : ini.findVariables(section)) { + if(prefix) + std::cout << '[' << section << ']'; + std::cout << var.first; + if(content) + std::cout << '=' << var.second; + std::cout << std::endl; + } + } + } else if(allvars) { + // Return all variables of certain name (global or from optional section) + std::vector strs; + switch(vtype) { + case TYPE_NONE: // If no --type on cmd-line we default to string + case TYPE_STRING: + strs = ini.findStringAll(variable, section); + break; + case TYPE_INTEGER: { + auto iv = ini.findSIntAll(variable, section); + for(auto const &i : iv) + strs.push_back(fmt::format("{}", i)); + } break; + case TYPE_UNSIGNED: { + auto iv = ini.findUIntAll(variable, section); + for(auto const &i : iv) + strs.push_back(fmt::format("{}", i)); + } break; + case TYPE_REAL: { + auto iv = ini.findRealAll(variable, section); + for(auto const &i : iv) + strs.push_back(fmt::format("{:.15g}", i)); + } break; + case TYPE_BOOLEAN: { + auto iv = ini.findBoolAll(variable, section); + for(auto const &i : iv) + strs.push_back(fmt::format("{}", i ? "true" : "false")); + } break; + default: + print_err(fmt::format("internal: Invalid type case (vtype={})", (int)vtype)); + break; + } + // Only test if num is actually set + if(num > 0 && num > (ssize_t)strs.size()) { + if(!quiet) { + if(strs.empty()) + print_err(fmt::format("Cannot find any instance of '[{}]{}'", section, variable)); + else + print_err(fmt::format("No instance {} or more of '[{}]{}'", num, section, variable)); + } + return EXIT_ENOENT; + } else if(num < 1) { + num = 1; // Was not set, start from the beginning + } + if(tildeexp && (TYPE_NONE == vtype || TYPE_STRING == vtype)) { + for(size_t i = num-1; i < strs.size(); i++) { + std::string ex; + if(IniFile::tildeExpand(strs[i], ex)) { + print_err(fmt::format("Tilde expansion failed on '{}'", strs[i])); + return EXIT_FAILURE; + } + std::cout << ex << std::endl; + } + } else { + for(size_t i = num-1; i < strs.size(); i++) + std::cout << strs[i] << std::endl; + } + } else { + // Print the num'th named variable (global or in optional section) + if(num <= 0) + num = 1; + if(auto str = ini.findString(num, variable, section)) { + // We get here and we know '[section]variable' exists, regardless content + switch(vtype) { + case TYPE_NONE: // If no --type on cmd-line we default to string + case TYPE_STRING: + if (tildeexp) { + std::string ex; + if(IniFile::tildeExpand(*str, ex)) { + print_err(fmt::format("Tilde expansion failed on '{}'", *str)); + return EXIT_FAILURE; + } + std::cout << ex << std::endl; + } else { + std::cout << *str << std::endl; + } + break; + case TYPE_INTEGER: { + rtapi_s64 smini = (havemm & HAVE_MINI) ? (rtapi_s64)mini : INT64_MIN; + rtapi_s64 smaxi = (havemm & HAVE_MAXI) ? (rtapi_s64)maxi : INT64_MAX; + if(smini > smaxi) { + print_err(fmt::format("Invalid integer range min '{}' > max '{}'", smini, smaxi)); + return EXIT_ERANGE; + } + if(auto i = ini.findSInt(num, variable, section, smini, smaxi)) { + std::cout << *i << std::endl; + } else { + print_err(fmt::format("Invalid integer [{}]{}='{}' or out of range [{},{}]", + section, variable, *str, smini, smaxi)); + return EXIT_ERANGE; + } + } break; + case TYPE_UNSIGNED: + if(!(havemm & HAVE_MINI)) + mini = 0; + if(!(havemm & HAVE_MAXI)) + maxi = UINT64_MAX; + if(mini > maxi) { + print_err(fmt::format("Invalid unsigned range min '{}' > max '{}'", mini, maxi)); + return EXIT_ERANGE; + } + if(auto i = ini.findUInt(num, variable, section, mini, maxi)) { + std::cout << *i << std::endl; + } else { + print_err(fmt::format("Invalid unsigned [{}]{}='{}' or out of range [{},{}]", + section, variable, *str, mini, maxi)); + return EXIT_ERANGE; + } + break; + case TYPE_REAL: + if(rmini > rmaxi) { + print_err(fmt::format("Invalid real range min '{}' > max '{}'", rmini, rmaxi)); + return EXIT_ERANGE; + } + if(auto i = ini.findReal(num, variable, section, rmini, rmaxi)) { + std::cout << fmt::format("{:.15e}", *i) << std::endl; + } else { + print_err(fmt::format("Invalid real [{}]{}='{}' or out of range [{:g},{:g}]", + section, variable, *str, rmini, rmaxi)); + return EXIT_ERANGE; + } + break; + case TYPE_BOOLEAN: + if(auto i = ini.findBool(num, variable, section)) { + if(boolnum) + std::cout << fmt::format("{}", *i ? "1" : "0") << std::endl; + else if(boolpy) + std::cout << fmt::format("{}", *i ? "True" : "False") << std::endl; + else + std::cout << fmt::format("{}", *i ? "true" : "false") << std::endl; + } else { + // Message was printed if failed conversion + // print_err(fmt::format("Invalid boolean '{}'", *str)); + return EXIT_ERANGE; + } + break; + default: + print_err(fmt::format("internal: Invalid type case (vtype={})", (int)vtype)); + break; + } + } else { + if(!quiet) { + print_err(fmt::format("Cannot find instance {} of '[{}]{}'", num, section, variable)); + } + return EXIT_ENOENT; + } + } + + return EXIT_SUCCESS; +} +// vim: ts=4 sw=4 diff --git a/src/emc/ini/inivar b/src/emc/ini/inivar new file mode 100755 index 00000000000..8f8e008db01 --- /dev/null +++ b/src/emc/ini/inivar @@ -0,0 +1,74 @@ +#!/bin/bash + +errexit() { + echo "inivar:" "$@" 1>&2; + exit 1 +} + +checkarganddup() { + if [ "$2" -ne 0 ]; then + errexit "Duplicate $1 option" + fi + if [ "$3" -lt 2 ]; then + errexit "Missing argument to $1" + fi +} + +DIR="$(dirname "$0")" +# Try to find the inivalue program +INIVALUE="$(command -v "$DIR/inivalue")" +if [ -z "$INIVALUE" ]; then + INIVALUE="./inivalue" +fi + +[ -x "$INIVALUE" ] || errexit "Program 'inivalue' not found" + +OPTS=( ) +HAVEVAR=0 +HAVENUM=0 +HAVESEC=0 +HAVEINI=0 + +# Map the 'inivar' options to 'inival' options +while [ $# -gt 0 ]; do + case "$1" in + -var) + checkarganddup "-var" "$HAVEVAR" "$#" + OPTS+=( "--var=$2" ) + HAVEVAR=1 + shift 2 + ;; + -sec) + checkarganddup "-sec" "$HAVESEC" "$#" + OPTS+=( "--sec=$2" ) + HAVESEC=1 + shift 2 + ;; + -ini) + checkarganddup "-ini" "$HAVEINI" "$#" + INIFILE="$2" + HAVEINI=1 + shift 2 + ;; + -num) + checkarganddup "-num" "$HAVENUM" "$#" + OPTS+=( "--num=$2" ) + HAVENUM=1 + shift 2 + ;; + -tildeexpand) + OPTS+=( "--tildeexpand" ) + shift + ;; + *) + errexit "Invalid option '$1'" + ;; + esac +done + +[ $HAVEVAR -eq 0 ] && errexit "No variable supplied (missing -var)" +[ $HAVEINI -eq 0 ] && INIFILE="emc.ini" + +exec "$INIVALUE" "${OPTS[@]}" "$INIFILE" + +# vim: ts=4 sw=4 diff --git a/src/emc/motion/usrmotintf.cc b/src/emc/motion/usrmotintf.cc index ffd7dc3a8f5..1585f394a28 100644 --- a/src/emc/motion/usrmotintf.cc +++ b/src/emc/motion/usrmotintf.cc @@ -28,7 +28,7 @@ #include "libnml/os_intf/_timer.h" #include "libnml/rcs/rcs_print.hh" -#include "libnml/inifile/inifile.hh" +#include #define READ_TIMEOUT_SEC 0 /* seconds for timeout */ #define READ_TIMEOUT_USEC 100000 /* microseconds for timeout */ @@ -36,6 +36,8 @@ #include "dbuf.h" #include "stashf.h" +using namespace linuxcnc; + static int inited = 0; /* flag if inited */ static emcmot_command_t *emcmotCommand = 0; @@ -49,24 +51,26 @@ static emcmot_struct_t *emcmotStruct = 0; from named INI file */ int usrmotIniLoad(const char *filename) { - IniFile inifile(IniFile::ERR_CONVERSION); // Enable exception. + IniFile inifile(filename); - /* open it */ - if (!inifile.Open(filename)) { - rtapi_print("can't find emcmot INI file %s\n", filename); - return -1; + if (!inifile) { + return -1; } - try { - inifile.Find((int *)&SHMEM_KEY, "SHMEM_KEY", "EMCMOT"); - inifile.Find(&EMCMOT_COMM_TIMEOUT, "COMM_TIMEOUT", "EMCMOT"); + if (inifile.isSet("SHMEM_KEY", "EMCMOT")) { + if (auto inival = inifile.findUInt("SHMEM_KEY", "EMCMOT")) { + SHMEM_KEY = *inival; + } else { + rcs_print("USRMOT: ERROR: Invalid [EMCMOT]SHMEM_KEY\n"); + } } - - catch(IniFile::Exception &e){ - e.Print(); - return -1; + if (inifile.isSet("COMM_TIMEOUT", "EMCMOT")) { + if (auto inival = inifile.findReal("COMM_TIMEOUT", "EMCMOT")) { + EMCMOT_COMM_TIMEOUT = *inival; + } else { + rcs_print("USRMOT: ERROR: Invalid [EMCMOT]COMM_TIMEOUT\n"); + } } - return 0; } diff --git a/src/emc/nml_intf/Submakefile b/src/emc/nml_intf/Submakefile index c33c620c9ce..2d2577ab7a4 100644 --- a/src/emc/nml_intf/Submakefile +++ b/src/emc/nml_intf/Submakefile @@ -7,7 +7,6 @@ LIBEMCSRCS := \ emc/nml_intf/emcargs.cc \ emc/nml_intf/emcops.cc \ emc/nml_intf/canon_position.cc \ - emc/ini/emcIniFile.cc \ emc/ini/iniaxis.cc \ emc/ini/inijoint.cc \ emc/ini/inispindle.cc \ diff --git a/src/emc/pythonplugin/Submakefile b/src/emc/pythonplugin/Submakefile index 93755ec1135..b38143caa15 100644 --- a/src/emc/pythonplugin/Submakefile +++ b/src/emc/pythonplugin/Submakefile @@ -13,7 +13,7 @@ $(call TOOBJSDEPS, $(LIBPPSRCS)) : EXTRAFLAGS=-fPIC $(BOOST_DEBUG_FLAGS) TARGETS += ../lib/libpyplugin.so.0 -../lib/libpyplugin.so.0: $(patsubst %.cc,objects/%.o,$(LIBPPSRCS)) ../lib/liblinuxcncini.so +../lib/libpyplugin.so.0: $(patsubst %.cc,objects/%.o,$(LIBPPSRCS)) ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) @mkdir -p ../lib @rm -f $@ diff --git a/src/emc/pythonplugin/python_plugin.cc b/src/emc/pythonplugin/python_plugin.cc index 227cb74ccc5..8139f82a90c 100644 --- a/src/emc/pythonplugin/python_plugin.cc +++ b/src/emc/pythonplugin/python_plugin.cc @@ -17,7 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "python_plugin.hh" -#include "libnml/inifile/inifile.hh" +#include #include #include @@ -31,6 +31,7 @@ #include namespace bp = boost::python; +using namespace linuxcnc; #define MAX_ERRMSG_SIZE 256 @@ -338,8 +339,6 @@ PythonPlugin::PythonPlugin(struct _inittab *inittab) : int PythonPlugin::configure(const char *iniFilename, const char *section) { - IniFile inifile; - if (section == NULL) { logPP(1, "no section"); status = PLUGIN_NO_SECTION; @@ -351,24 +350,25 @@ int PythonPlugin::configure(const char *iniFilename, status = PLUGIN_NO_INIFILE; return status; } - if (inifile.Open(iniFilename) == false) { + + IniFile inifile(iniFilename); + if (!inifile) { logPP(-1, "Unable to open inifile:%s:\n", iniFilename); status = PLUGIN_BAD_INIFILE; return status; } char real_path[PATH_MAX]; - char expandinistring[PATH_MAX]; - if (auto inistring = inifile.Find("TOPLEVEL", section)) { - if (inifile.TildeExpansion(inistring->c_str(),expandinistring,sizeof(expandinistring))) { + std::string expandinistring; + if (auto inistring = inifile.findString("TOPLEVEL", section)) { + if (inifile.TildeExpansion(*inistring, expandinistring)) { logPP(-1, "TildeExpansion failed '%s'", toplevel); status = PLUGIN_BAD_PATH; return status; } - toplevel = strstore(expandinistring); + toplevel = strstore(expandinistring.c_str()); - if (auto reload_str = inifile.Find("RELOAD_ON_CHANGE", section)) - reload_on_change = (atoi(reload_str->c_str()) > 0); + reload_on_change = inifile.findBoolV("RELOAD_ON_CHANGE", section, false); if (realpath(toplevel, real_path) == NULL) { logPP(-1, "can\'t resolve path to '%s'", toplevel); @@ -393,25 +393,22 @@ int PythonPlugin::configure(const char *iniFilename, abs_path = strstore(real_path); } - if (auto inistring = inifile.Find("LOG_LEVEL", section)) - log_level = atoi(inistring->c_str()); - else log_level = 0; + log_level = inifile.findIntV("LOG_LEVEL", section, 0); char pycmd[PATH_MAX + 64]; int n = 1; - int lineno; - while (auto inistring = inifile.Find("PATH_PREPEND", "PYTHON", - n, &lineno)) { - if (inifile.TildeExpansion(inistring->c_str(),expandinistring,sizeof(expandinistring))) { + while (auto inistring = inifile.findString(n, "PATH_PREPEND", "PYTHON")) { + auto lineno = inifile.lineOf(n, "PATH_PREPEND", "PYTHON"); + if (inifile.TildeExpansion(*inistring, expandinistring)) { logPP(-1, "TildeExpansion failed '%s'", toplevel); status = PLUGIN_EXCEPTION_DURING_PATH_PREPEND; return status; } - snprintf(pycmd, sizeof(pycmd), "import sys\nsys.path.insert(0,\"%s\")", expandinistring); - logPP(1, "%s:%d: executing '%s'",iniFilename, lineno, pycmd); + snprintf(pycmd, sizeof(pycmd), "import sys\nsys.path.insert(0,\"%s\")", expandinistring.c_str()); + logPP(1, "%s:%d: executing '%s'", lineno.first.c_str(), lineno.second, pycmd); if (PyRun_SimpleString(pycmd)) { - logPP(-1, "%s:%d: exception running '%s'",iniFilename, lineno, pycmd); + logPP(-1, "%s:%d: exception running '%s'", lineno.first.c_str(), lineno.second, pycmd); exception_msg = "exception running:" + std::string((const char*)pycmd); status = PLUGIN_EXCEPTION_DURING_PATH_PREPEND; return status; @@ -419,17 +416,17 @@ int PythonPlugin::configure(const char *iniFilename, n++; } n = 1; - while (auto inistring = inifile.Find("PATH_APPEND", "PYTHON", - n, &lineno)) { - if (inifile.TildeExpansion(inistring->c_str(),expandinistring,sizeof(expandinistring))) { + while (auto inistring = inifile.findString(n, "PATH_APPEND", "PYTHON")) { + auto lineno = inifile.lineOf(n, "PATH_APPEND", "PYTHON"); + if (inifile.TildeExpansion(*inistring, expandinistring)) { logPP(-1, "TildeExpansion failed '%s'", toplevel); status = PLUGIN_EXCEPTION_DURING_PATH_APPEND; return status; } - snprintf(pycmd, sizeof(pycmd), "import sys\nsys.path.append(\"%s\")", expandinistring); - logPP(1, "%s:%d: executing '%s'",iniFilename, lineno, pycmd); + snprintf(pycmd, sizeof(pycmd), "import sys\nsys.path.append(\"%s\")", expandinistring.c_str()); + logPP(1, "%s:%d: executing '%s'", lineno.first.c_str(), lineno.second, pycmd); if (PyRun_SimpleString(pycmd)) { - logPP(-1, "%s:%d: exception running '%s'",iniFilename, lineno, pycmd); + logPP(-1, "%s:%d: exception running '%s'", lineno.first.c_str(), lineno.second, pycmd); exception_msg = "exception running " + std::string((const char*)pycmd); status = PLUGIN_EXCEPTION_DURING_PATH_APPEND; return status; diff --git a/src/emc/rs274ngc/interp_namedparams.cc b/src/emc/rs274ngc/interp_namedparams.cc index 4cc496bd8b1..9fcc602a9c4 100644 --- a/src/emc/rs274ngc/interp_namedparams.cc +++ b/src/emc/rs274ngc/interp_namedparams.cc @@ -41,17 +41,20 @@ namespace bp = boost::python; #include #include #include +#include #include #include "rs274ngc.hh" #include "rs274ngc_return.hh" #include "interp_internal.hh" #include "rs274ngc_interp.hh" -#include "libnml/inifile/inifile.hh" +#include // for HAL pin variables #include +using namespace linuxcnc; + enum predefined_named_parameters { NP_LINE, NP_MOTION_MODE, @@ -193,45 +196,44 @@ int Interp::read_named_parameter( // the shortest possible INI variable is '_ini[s]n' or 8 chars long . int Interp::fetch_ini_param( const char *nameBuf, int *status, double *value) { - char *s; *status = 0; int n = strlen(nameBuf); - if ((n > 7) && - ((s = (char *) strchr(&nameBuf[6],']')) != NULL)) { - - IniFile inifile; - const char *iniFileName; - int retval; - int closeBracket = s - nameBuf; - - if ((iniFileName = getenv("INI_FILE_NAME")) == NULL) { - logNP("warning: referencing INI parameter '%s': no INI file",nameBuf); - *status = 0; - return INTERP_OK; - } - if (!inifile.Open(iniFileName)) { - *status = 0; - ERS(_("can\'t open INI file '%s'"), iniFileName); - } + if(n < 8) { + return INTERP_OK; + } - char capName[LINELEN]; + std::string sect = nameBuf + 5; // skip the '_ini[' part + // Make it all upper case + for(auto &c : sect) { + c = toupper(c); + } + size_t i = sect.find(']'); + if(std::string::npos == i) { + ERS(_("_ini expansion missing ']'")); + return INTERP_OK; + } + std::string var = sect.substr(i+1); + sect.erase(i); // Remove everything from ']'and after - snprintf(capName, LINELEN, "%s", nameBuf); - for (char *p = capName; *p != 0; p++) - *p = toupper(*p); - capName[closeBracket] = '\0'; + const char *iniFileName; + if ((iniFileName = getenv("INI_FILE_NAME")) == NULL) { + logNP("warning: referencing INI parameter '%s': no INI file", nameBuf); + return INTERP_OK; + } + IniFile inifile(iniFileName); + if (!inifile) { + ERS(_("can\'t open INI file '%s'"), iniFileName); + return INTERP_OK; + } - if ((retval = inifile.Find( value, &capName[closeBracket+1], &capName[5])) == 0) { - *status = 1; - inifile.Close(); - } else { - inifile.Close(); - *status = 0; - ERS(_("Named INI parameter #<%s> not found in INI file '%s': error=0x%x"), - nameBuf, iniFileName, retval); - } + if (auto inival = inifile.findReal(var, sect)) { + *value = *inival; + *status = 1; + } else { + ERS(_("Named INI parameter #<%s> not found in INI file '%s'"), nameBuf, iniFileName); } + return INTERP_OK; } @@ -300,6 +302,8 @@ int Interp::fetch_hal_param( const char *nameBuf, int *status, double *value) case HAL_BIT: *value = (double) (ptr->b); break; case HAL_U32: *value = (double) (ptr->u); break; case HAL_S32: *value = (double) (ptr->s); break; + case HAL_U64: *value = (double) (ptr->lu); break; + case HAL_S64: *value = (double) (ptr->ls); break; case HAL_FLOAT: *value = (double) (ptr->f); break; default: return -1; } @@ -962,28 +966,24 @@ int Interp::init_named_parameters() double Interp::inicheck() { - IniFile inifile; const char *filename; - double result = -1.0; - if ((filename = getenv("INI_FILE_NAME")) == NULL) { - return -1.0; + if ((filename = getenv("INI_FILE_NAME")) == NULL) { + return -1.0; } - // open it - if (inifile.Open(filename) == false) { - return -1.0; + IniFile inifile(filename); + if (!inifile) { + return -1.0; } - if (auto inistring = inifile.Find("LINEAR_UNITS", "TRAJ")) { + if (auto inistring = inifile.findString("LINEAR_UNITS", "TRAJ")) { if (*inistring == "inch") { - result = 0.0; + return 0.0; } else { - result = 1.0; + return 1.0; } } - // close it - inifile.Close(); - return result; + return -1.0; } diff --git a/src/emc/rs274ngc/rs274ngc_pre.cc b/src/emc/rs274ngc/rs274ngc_pre.cc index f0507aa6695..e2f798f161f 100644 --- a/src/emc/rs274ngc/rs274ngc_pre.cc +++ b/src/emc/rs274ngc/rs274ngc_pre.cc @@ -92,7 +92,7 @@ include an option for suppressing superfluous commands. #include // rtapi_strlcpy() #include -#include "libnml/inifile/inifile.hh" // INIFILE +#include #include "rs274ngc.hh" #include "rs274ngc_return.hh" #include "interp_internal.hh" // interpreter private definitions @@ -105,6 +105,7 @@ include an option for suppressing superfluous commands. #include "interp_parameter_def.hh" using namespace interp_param_global; +using namespace linuxcnc; namespace bp = boost::python; @@ -837,7 +838,6 @@ int Interp::init() char filename[LINELEN]; double *pars; // short name for _setup.parameters char *iniFileName; - IniFile::ErrorCode r; INIT_CANON(); @@ -878,71 +878,60 @@ int Interp::init() _setup.center_arc_radius_tolerance_mm = CENTER_ARC_RADIUS_TOLERANCE_MM; if(iniFileName != NULL) { - - IniFile inifile; - if (inifile.Open(iniFileName) == false) { + IniFile inifile(iniFileName); + if (!inifile) { fprintf(stderr,"Unable to open inifile:%s:\n", iniFileName); } else { - bool opt; - - inifile.Find(&_setup.tool_change_at_g30, "TOOL_CHANGE_AT_G30", "EMCIO"); - inifile.Find(&_setup.tool_change_quill_up, "TOOL_CHANGE_QUILL_UP", "EMCIO"); - inifile.Find(&_setup.tool_change_with_spindle_on, "TOOL_CHANGE_WITH_SPINDLE_ON", "EMCIO"); - inifile.Find(&_setup.a_axis_wrapped, "WRAPPED_ROTARY", "AXIS_A"); - inifile.Find(&_setup.b_axis_wrapped, "WRAPPED_ROTARY", "AXIS_B"); - inifile.Find(&_setup.c_axis_wrapped, "WRAPPED_ROTARY", "AXIS_C"); - inifile.Find(&_setup.random_toolchanger, "RANDOM_TOOLCHANGER", "EMCIO"); - inifile.Find(&_setup.num_spindles, "SPINDLES", "TRAJ"); - - inifile.Find(&_setup.tolerance_default, "G64_DEFAULT_TOLERANCE", "RS274NGC"); - inifile.Find(&_setup.naivecam_tolerance_default, "G64_DEFAULT_NAIVETOLERANCE", "RS274NGC"); + _setup.tool_change_at_g30 = inifile.findBoolV("TOOL_CHANGE_AT_G30", "EMCIO", false); + _setup.tool_change_quill_up = inifile.findBoolV("TOOL_CHANGE_QUILL_UP", "EMCIO", false); + _setup.tool_change_with_spindle_on = inifile.findBoolV("TOOL_CHANGE_WITH_SPINDLE_ON", "EMCIO", false); + _setup.a_axis_wrapped = inifile.findBoolV("WRAPPED_ROTARY", "AXIS_A", false); + _setup.b_axis_wrapped = inifile.findBoolV("WRAPPED_ROTARY", "AXIS_B", false); + _setup.c_axis_wrapped = inifile.findBoolV("WRAPPED_ROTARY", "AXIS_C", false); + _setup.random_toolchanger = inifile.findBoolV("RANDOM_TOOLCHANGER", "EMCIO", false); + _setup.num_spindles = inifile.findIntV("SPINDLES", "TRAJ", 1); + + _setup.tolerance_default = inifile.findRealV("G64_DEFAULT_TOLERANCE", "RS274NGC", 0.0); + _setup.naivecam_tolerance_default = inifile.findRealV("G64_DEFAULT_NAIVETOLERANCE", "RS274NGC", 0.0); // First the features that default to ON - opt = true; - inifile.Find(&opt, "INI_VARS", "RS274NGC"); - if (opt) _setup.feature_set |= FEATURE_INI_VARS; - opt = true; - inifile.Find(&opt, "HAL_PIN_VARS", "RS274NGC"); - if (opt) _setup.feature_set |= FEATURE_HAL_PIN_VARS; + if (inifile.findBoolV("INI_VARS", "RS274NGC", true)) + _setup.feature_set |= FEATURE_INI_VARS; + if (inifile.findBoolV("HAL_PIN_VARS", "RS274NGC", true)) + _setup.feature_set |= FEATURE_HAL_PIN_VARS; // Now those that (currently) default to off - opt = false; - inifile.Find(&opt, "RETAIN_G43", "RS274NGC"); - if (opt) _setup.feature_set |= FEATURE_RETAIN_G43; - opt = false; - inifile.Find(&opt, "OWORD_NARGS", "RS274NGC"); - if (opt) _setup.feature_set |= FEATURE_OWORD_N_ARGS; - opt = false; - inifile.Find(&opt, "NO_DOWNCASE_OWORD", "RS274NGC"); - if (opt) _setup.feature_set |= FEATURE_NO_DOWNCASE_OWORD; - opt = false; - inifile.Find(&opt, "OWORD_WARNONLY", "RS274NGC"); - if (opt) _setup.feature_set |= FEATURE_OWORD_WARNONLY; - - if (auto inistring = inifile.Find("LOCKING_INDEXER_JOINT", "AXIS_A")) { - _setup.a_indexer_jnum = atol(inistring->c_str()); + if (inifile.findBoolV("RETAIN_G43", "RS274NGC", false)) + _setup.feature_set |= FEATURE_RETAIN_G43; + if (inifile.findBoolV("OWORD_NARGS", "RS274NGC", false)) + _setup.feature_set |= FEATURE_OWORD_N_ARGS; + if (inifile.findBoolV("NO_DOWNCASE_OWORD", "RS274NGC", false)) + _setup.feature_set |= FEATURE_NO_DOWNCASE_OWORD; + if (inifile.findBoolV("OWORD_WARNONLY", "RS274NGC", false)) + _setup.feature_set |= FEATURE_OWORD_WARNONLY; + + if (auto inival = inifile.findInt("LOCKING_INDEXER_JOINT", "AXIS_A")) { + _setup.a_indexer_jnum = *inival; } - if (auto inistring = inifile.Find("LOCKING_INDEXER_JOINT", "AXIS_B")) { - _setup.b_indexer_jnum = atol(inistring->c_str()); + if (auto inival = inifile.findInt("LOCKING_INDEXER_JOINT", "AXIS_B")) { + _setup.b_indexer_jnum = *inival; } - if (auto inistring = inifile.Find("LOCKING_INDEXER_JOINT", "AXIS_C")) { - _setup.c_indexer_jnum = atol(inistring->c_str()); + if (auto inival = inifile.findInt("LOCKING_INDEXER_JOINT", "AXIS_C")) { + _setup.c_indexer_jnum = *inival; } - inifile.Find(&_setup.orient_offset, "ORIENT_OFFSET", "RS274NGC"); - inifile.Find(&_setup.parameter_g73_peck_clearance, "G73_PECK_CLEARANCE", "RS274NGC"); - inifile.Find(&_setup.parameter_g83_peck_clearance, "G83_PECK_CLEARANCE", "RS274NGC"); + _setup.orient_offset = inifile.findRealV("ORIENT_OFFSET", "RS274NGC", 0.0); + double clr = _setup.length_units == CANON_UNITS_INCHES ? 0.050 : 1.0; + _setup.parameter_g73_peck_clearance = inifile.findRealV("G73_PECK_CLEARANCE", "RS274NGC", clr); + _setup.parameter_g83_peck_clearance = inifile.findRealV("G83_PECK_CLEARANCE", "RS274NGC", clr); - inifile.Find(&_setup.debugmask, "DEBUG", "EMC"); + _setup.debugmask = inifile.findUIntV("DEBUG", "EMC", 0); - _setup.debugmask |= EMC_DEBUG_UNCONDITIONAL; + _setup.debugmask |= EMC_DEBUG_UNCONDITIONAL; - if(auto inistring = inifile.Find("LOG_LEVEL", "RS274NGC")) - { - _setup.loggingLevel = atol(inistring->c_str()); - } + _setup.loggingLevel = inifile.findIntV("LOG_LEVEL", "RS274NGC", 0); // default the log_file to stderr. - if(auto inistring = inifile.Find("LOG_FILE", "RS274NGC")) + if(auto inistring = inifile.findString("LOG_FILE", "RS274NGC")) { if ((log_file = fopen(inistring->c_str(), "a")) == NULL) { log_file = stderr; @@ -956,7 +945,7 @@ int Interp::init() _setup.use_lazy_close = 1; _setup.wizard_root[0] = 0; - if(auto inistring = inifile.Find("WIZARD_ROOT", "WIZARD")) + if(auto inistring = inifile.findString("WIZARD_ROOT", "WIZARD")) { logDebug("[WIZARD]WIZARD_ROOT:%s", inistring->c_str()); if (realpath(inistring->c_str(), _setup.wizard_root) == NULL) { @@ -967,14 +956,14 @@ int Interp::init() logDebug("_setup.wizard_root:%s:", _setup.wizard_root); _setup.program_prefix[0] = 0; - if(auto inistring = inifile.Find("PROGRAM_PREFIX", "DISPLAY")) + if(auto inistring = inifile.findString("PROGRAM_PREFIX", "DISPLAY")) { // found it - char expandinistring[LINELEN]; - if (inifile.TildeExpansion(inistring->c_str(),expandinistring,sizeof(expandinistring))) { + std::string expandinistring; + if (inifile.TildeExpansion(*inistring, expandinistring)) { logDebug("TildeExpansion failed for: %s",inistring->c_str()); } - if (realpath(expandinistring, _setup.program_prefix) == NULL){ + if (realpath(expandinistring.c_str(), _setup.program_prefix) == NULL){ //realpath didn't find the file logDebug("realpath failed to find program_prefix:%s:", inistring->c_str()); } @@ -987,7 +976,7 @@ int Interp::init() } logDebug("_setup.program_prefix:%s:", _setup.program_prefix); - if(auto inistring = inifile.Find("SUBROUTINE_PATH", "RS274NGC")) + if(auto inistring = inifile.findString("SUBROUTINE_PATH", "RS274NGC")) { // found it int dct; @@ -1003,11 +992,11 @@ int Interp::init() dct = 0; while (1) { char tmp_path[PATH_MAX]; - char expandnextdir[LINELEN]; - if (inifile.TildeExpansion(nextdir,expandnextdir,sizeof(expandnextdir))) { + std::string expandnextdir; + if (inifile.TildeExpansion(nextdir, expandnextdir)) { logDebug("TildeExpansion failed for: %s",nextdir); } - if (realpath(expandnextdir, tmp_path) == NULL){ + if (realpath(expandnextdir.c_str(), tmp_path) == NULL){ //realpath didn't find the directory logDebug("realpath failed to find subroutines[%d]:%s:",dct,nextdir); _setup.subroutines[dct] = NULL; @@ -1030,29 +1019,31 @@ int Interp::init() } // subroutine to execute on aborts - for instance to retract // toolchange HAL pins - if (auto inistring = inifile.Find("ON_ABORT_COMMAND", "RS274NGC")) { - _setup.on_abort_command = strstore(inistring->c_str()); + if (auto inistring = inifile.findString("ON_ABORT_COMMAND", "RS274NGC")) { + _setup.on_abort_command = strstore(inistring->c_str()); logDebug("_setup.on_abort_command=%s", _setup.on_abort_command); } else { - _setup.on_abort_command = NULL; + _setup.on_abort_command = NULL; } - // initialize the Python plugin singleton - if (inifile.Find("TOPLEVEL", "PYTHON")) { - int status = python_plugin->configure(iniFileName,"PYTHON"); - if (status != PLUGIN_OK) { - Error("Python plugin configure() failed, status = %d", status); - } - } + // initialize the Python plugin singleton + if (inifile.isSet("TOPLEVEL", "PYTHON")) { + int status = python_plugin->configure(iniFileName,"PYTHON"); + if (status != PLUGIN_OK) { + Error("Python plugin configure() failed, status = %d", status); + } + } int n = 1; - int lineno = -1; _setup.g_remapped.clear(); _setup.m_remapped.clear(); _setup.remaps.clear(); - while (auto inistring = inifile.Find("REMAP", "RS274NGC", - n, &lineno)) { - + while (auto inistring = inifile.findString(n, "REMAP", "RS274NGC")) { + // FIXME: This should make use of the path/lineno pair returned + // by lineOf() because it may be coming from an include file. + // However, it does require some extra work in + // interp_remap.cc:parse_remap() to handle and use the extra arg. + int lineno = inifile.lineOf(n, "REMAP", "RS274NGC").second; CHP(parse_remap( inistring->c_str(), lineno)); n++; } @@ -1060,42 +1051,32 @@ int Interp::init() // if exist and within bounds, apply INI file arc tolerances // limiting figures are defined in interp_internal.hh - r = inifile.Find( - &_setup.center_arc_radius_tolerance_inch, - MIN_CENTER_ARC_RADIUS_TOLERANCE_INCH, - CENTER_ARC_RADIUS_TOLERANCE_INCH, - "CENTER_ARC_RADIUS_TOLERANCE_INCH", - "RS274NGC" - ); - if ((r != IniFile::ERR_NONE) && (r != IniFile::ERR_TAG_NOT_FOUND)) { - Error("invalid [RS274NGC]CENTER_ARC_RADIUS_TOLERANCE_INCH in INI file\n"); + if (inifile.isSet("CENTER_ARC_RADIUS_TOLERANCE_INCH", "RS274NGC")) { + if (auto inival = inifile.findReal("CENTER_ARC_RADIUS_TOLERANCE_INCH", "RS274NGC", + MIN_CENTER_ARC_RADIUS_TOLERANCE_INCH, + CENTER_ARC_RADIUS_TOLERANCE_INCH)) { + _setup.center_arc_radius_tolerance_inch = *inival; + } else { + Error("invalid [RS274NGC]CENTER_ARC_RADIUS_TOLERANCE_INCH in INI file\n"); + } } - r = inifile.Find( - &_setup.center_arc_radius_tolerance_mm, - MIN_CENTER_ARC_RADIUS_TOLERANCE_MM, - CENTER_ARC_RADIUS_TOLERANCE_MM, - "CENTER_ARC_RADIUS_TOLERANCE_MM", - "RS274NGC" - ); - if ((r != IniFile::ERR_NONE) && (r != IniFile::ERR_TAG_NOT_FOUND)) { - Error("invalid [RS274NGC]CENTER_ARC_RADIUS_TOLERANCE_MM in INI file\n"); + if (inifile.isSet("CENTER_ARC_RADIUS_TOLERANCE_MM", "RS274NGC")) { + if (auto inival = inifile.findReal("CENTER_ARC_RADIUS_TOLERANCE_MM", "RS274NGC", + MIN_CENTER_ARC_RADIUS_TOLERANCE_MM, + CENTER_ARC_RADIUS_TOLERANCE_MM)) { + _setup.center_arc_radius_tolerance_mm = *inival; + } else { + Error("invalid [RS274NGC]CENTER_ARC_RADIUS_TOLERANCE_MM in INI file\n"); + } } - // INI file g52/g92 offset persistence default setting - inifile.Find(&_setup.disable_g92_persistence, - "DISABLE_G92_PERSISTENCE", - "RS274NGC"); - - // INI file m98/m99 subprogram default setting - inifile.Find(&_setup.disable_fanuc_style_sub, - "DISABLE_FANUC_STYLE_SUB", - "RS274NGC"); - logDebug("init: DISABLE_FANUC_STYLE_SUB = %d", - _setup.disable_fanuc_style_sub); + // INI file g52/g92 offset persistence default setting + _setup.disable_g92_persistence = inifile.findBoolV("DISABLE_G92_PERSISTENCE", "RS274NGC", false); - // close it - inifile.Close(); + // INI file m98/m99 subprogram default setting + _setup.disable_fanuc_style_sub = inifile.findBoolV("DISABLE_FANUC_STYLE_SUB", "RS274NGC", false); + logDebug("init: DISABLE_FANUC_STYLE_SUB = %d", _setup.disable_fanuc_style_sub); } } @@ -2511,19 +2492,17 @@ VARIABLE_FILE = rs274ngc.var int Interp::ini_load(const char *filename) { - IniFile inifile; + IniFile inifile(filename); - // open it - if (inifile.Open(filename) == false) { + if (!inifile) { logDebug("Unable to open inifile:%s:", filename); - return -1; + return -1; } logDebug("Opened inifile:%s:", filename); - char parameter_file_name[LINELEN]={}; - if (auto inistring = inifile.Find("PARAMETER_FILE", "RS274NGC")) { + if (auto inistring = inifile.findString("PARAMETER_FILE", "RS274NGC")) { if (inistring->length() >= sizeof(parameter_file_name)) { logDebug("%s:[RS274NGC]PARAMETER_FILE is too long (max len %zu)", filename, sizeof(parameter_file_name)-1); @@ -2538,9 +2517,6 @@ int Interp::ini_load(const char *filename) } SET_PARAMETER_FILE_NAME(parameter_file_name); - // close it - inifile.Close(); - CHKS((strlen(parameter_file_name) == 0), _("Parameter file name is missing")); return 0; diff --git a/src/emc/sai/Submakefile b/src/emc/sai/Submakefile index 9c07b95d613..6c1cad78e88 100644 --- a/src/emc/sai/Submakefile +++ b/src/emc/sai/Submakefile @@ -5,6 +5,6 @@ SAISRCS := $(addprefix emc/sai/, saicanon.cc driver.cc dummyemcstat.cc) \ USERSRCS += $(SAISRCS) ../bin/rs274: $(call TOOBJS, $(SAISRCS)) ../lib/librs274.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 \ - ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.0 ../lib/libpyplugin.so.0 ../lib/libtooldata.so.0 + ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.1 ../lib/libpyplugin.so.0 ../lib/libtooldata.so.0 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) $(PYTHON_EXTRA_LDFLAGS) -o $@ $^ $(ULFLAGS) $(BOOST_PYTHON_LIB) $(PYTHON_LIBS) $(PYTHON_EXTRA_LIBS) $(READLINE_LIBS) $(LDFLAGS) diff --git a/src/emc/sai/driver.cc b/src/emc/sai/driver.cc index 0496c5f693a..adae9af7af2 100644 --- a/src/emc/sai/driver.cc +++ b/src/emc/sai/driver.cc @@ -21,7 +21,7 @@ #include "rs274ngc/rs274ngc.hh" #include "rs274ngc/rs274ngc_interp.hh" #include "rs274ngc/rs274ngc_return.hh" -#include "libnml/inifile/inifile.hh" // INIFILE +#include #include "nml_intf/canon.hh" // _parameter_file_name #include "config.h" // LINELEN #include /* gets, etc. */ @@ -40,6 +40,8 @@ #include "saicanon.hh" #include "tooldata/tooldata.hh" +using namespace linuxcnc; + InterpBase *pinterp; #define interp_new (*pinterp) const char *prompt = "READ => "; @@ -675,14 +677,13 @@ int main (int argc, char ** argv) } _sai._external_length_units = 0.03937007874016; if (inifile!= 0) { - IniFile ini; - // open it - if (ini.Open(inifile) == false) { - fprintf(stderr, "could not open supplied INI file %s\n", inifile); + IniFile ini(inifile); + if (!ini) { + fprintf(stderr, "could not open supplied INI file %s\n", inifile); exit(1); } - if (auto inistring = ini.Find("LINEAR_UNITS", "TRAJ")) { + if (auto inistring = ini.findString("LINEAR_UNITS", "TRAJ")) { if (*inistring == "mm") { _sai._external_length_units = 1.0; } diff --git a/src/emc/task/Submakefile b/src/emc/task/Submakefile index fabd1cd4a12..44ea2ba8ac5 100644 --- a/src/emc/task/Submakefile +++ b/src/emc/task/Submakefile @@ -1,9 +1,11 @@ EMCSVRSRCS := \ - emc/task/emcsvr.cc + emc/task/emcsvr.cc \ + emc/usr_intf/mapini.cc + USERSRCS += $(EMCSVRSRCS) -../bin/linuxcncsvr: $(call TOOBJS, $(EMCSVRSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.0 +../bin/linuxcncsvr: $(call TOOBJS, $(EMCSVRSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -o $@ $^ $(LDFLAGS) TARGETS += ../bin/linuxcncsvr @@ -21,17 +23,18 @@ MILLTASKSRCS := \ emc/motion/stashf.c \ emc/task/taskclass.cc \ emc/task/backtrace.cc \ + emc/usr_intf/mapini.cc USERSRCS += $(MILLTASKSRCS) #LDFLAGS += ../bin/milltask: $(call TOOBJS, $(MILLTASKSRCS)) ../lib/librs274.so.0 ../lib/liblinuxcnc.a \ - ../lib/libnml.so.0 ../lib/liblinuxcncini.so.0 ../lib/libposemath.so.0 \ + ../lib/libnml.so.0 ../lib/liblinuxcncini.so.1 ../lib/libposemath.so.0 \ ../lib/liblinuxcnchal.so.0 ../lib/libpyplugin.so.0 \ ../lib/libtooldata.so.0 $(ECHO) Linking $(notdir $@) - $(CXX) -o $@ $^ $(LDFLAGS) $(PYTHON_EXTRA_LDFLAGS) $(BOOST_PYTHON_LIB) $(PYTHON_LIBS) $(PYTHON_EXTRA_LIBS) + $(CXX) -o $@ $^ $(LDFLAGS) $(PYTHON_EXTRA_LDFLAGS) $(BOOST_PYTHON_LIB) $(PYTHON_LIBS) $(PYTHON_EXTRA_LIBS) -lfmt TARGETS += ../bin/milltask diff --git a/src/emc/task/emcsvr.cc b/src/emc/task/emcsvr.cc index 9fcf42dcc21..43903d739df 100644 --- a/src/emc/task/emcsvr.cc +++ b/src/emc/task/emcsvr.cc @@ -13,63 +13,38 @@ * Last change: ********************************************************************/ -#include // sscanf() -#include // fabs() -#include // exit() -#include // strncpy() -#include // _exit() #include #include "libnml/rcs/rcs.hh" // EMC NML #include "nml_intf/emc.hh" // EMC NML #include "nml_intf/emc_nml.hh" // EMC NML #include "nml_intf/emcglb.h" // emcGetArgs(), EMC_NMLFILE -#include "libnml/inifile/inifile.hh" +#include #include "libnml/rcs/rcs_print.hh" #include "libnml/nml/nml_oi.hh" #include "libnml/os_intf/timer.hh" #include "libnml/nml/nml_srv.hh" // run_nml_servers() #include +#include "usr_intf/mapini.hh" + +using namespace linuxcnc; static int iniLoad(const char *filename) { - IniFile inifile; - char version[LINELEN], machine[LINELEN]; + IniFile inifile(filename); - // open it - if (inifile.Open(filename) == false) { + if (!inifile) { return -1; } // EMC debugging flags - emc_debug = 0; // disabled by default - if (auto inistring = inifile.Find("DEBUG", "EMC")) { - // parse to global - if (sscanf(inistring->c_str(), "%x", &emc_debug) < 1) { - perror("failed to parse [EMC] DEBUG"); - } - } + emc_debug = inifile.findUIntV("DEBUG", "EMC", 0); // set output for RCS messages - set_rcs_print_destination(RCS_PRINT_TO_STDOUT); // use stdout by default - if (auto inistring = inifile.Find("RCS_DEBUG_DEST", "EMC")) { - static RCS_PRINT_DESTINATION_TYPE type; - if (*inistring == "STDOUT") { - type = RCS_PRINT_TO_STDOUT; - } else if (*inistring == "STDERR") { - type = RCS_PRINT_TO_STDERR; - } else if (*inistring == "FILE") { - type = RCS_PRINT_TO_FILE; - } else if (*inistring == "LOGGER") { - type = RCS_PRINT_TO_LOGGER; - } else if (*inistring == "MSGBOX") { - type = RCS_PRINT_TO_MESSAGE_BOX; - } else if (*inistring == "NULL") { - type = RCS_PRINT_TO_NULL; - } else { - type = RCS_PRINT_TO_STDOUT; - } - set_rcs_print_destination(type); + if (auto inival = mapRcsDestination(inifile, "RCS_DEBUG_DEST", "EMC")) { + set_rcs_print_destination(*inival); + } else { + set_rcs_print_destination(RCS_PRINT_TO_STDOUT); } // NML/RCS debugging flags @@ -81,46 +56,29 @@ static int iniLoad(const char *filename) } // set flags if RCS_DEBUG in ini file - if (auto inistring = inifile.Find("RCS_DEBUG", "EMC")) { - long unsigned int flags; - if (sscanf(inistring->c_str(), "%lx", &flags) < 1) { - perror("failed to parse [EMC] RCS_DEBUG"); - } + if (auto inival = inifile.findUInt("RCS_DEBUG", "EMC")) { // clear all flags clear_rcs_print_flag(PRINT_EVERYTHING); // set parsed flags - set_rcs_print_flag((long)flags); + set_rcs_print_flag((long)*inival); } // output infinite RCS errors by default - max_rcs_errors_to_print = -1; - if (auto inistring = inifile.Find("RCS_MAX_ERR", "EMC")) { - if (sscanf(inistring->c_str(), "%d", &max_rcs_errors_to_print) < 1) { - perror("failed to parse [EMC] RCS_MAX_ERR"); - } - } + max_rcs_errors_to_print = inifile.findIntV("RCS_MAX_ERR", "EMC", -1); if (emc_debug & EMC_DEBUG_CONFIG) { - auto ver = inifile.Find("VERSION", "EMC"); - strncpy(version, ver ? ver->c_str() : "unknown", LINELEN-1); - - auto mach = inifile.Find("MACHINE", "EMC"); - strncpy(machine, mach ? mach->c_str() : "unknown", LINELEN-1); - + std::string version = inifile.findStringV("VERSION", "EMC", ""); + std::string machine = inifile.findStringV("MACHINE", "EMC", ""); extern char *program_invocation_short_name; rcs_print( "%s (%d) emcsvr: machine '%s' version '%s'\n", - program_invocation_short_name, getpid(), machine, version + program_invocation_short_name, getpid(), machine.c_str(), version.c_str() ); } - if (auto inistring = inifile.Find("NML_FILE", "EMC")) { + if (auto inistring = inifile.findString("NML_FILE", "EMC")) { // copy to global rtapi_strxcpy(emc_nmlfile, inistring->c_str()); - } else { - // not found, use default - } - // close it - inifile.Close(); + } // else not found, use default if(emc_debug & EMC_DEBUG_CONFIG) rcs_print("config file \"%s\" loaded successfully.\n", filename); diff --git a/src/emc/task/emctask.cc b/src/emc/task/emctask.cc index 178100656dd..90fc85d0be5 100644 --- a/src/emc/task/emctask.cc +++ b/src/emc/task/emctask.cc @@ -29,12 +29,14 @@ #include "nml_intf/canon.hh" // CANON_VECTOR, GET_PROGRAM_ORIGIN() #include "rs274ngc/rs274ngc_interp.hh" // the interpreter #include "nml_intf/interp_return.hh" // INTERP_FILE_NOT_OPEN -#include "libnml/inifile/inifile.hh" +#include #include "libnml/rcs/rcs_print.hh" #include "task.hh" // emcTaskCommand etc #include "taskclass.hh" #include "motion/motion.h" +using namespace linuxcnc; + #define USER_DEFINED_FUNCTION_MAX_DIRS 5 #define MAX_M_DIRS (USER_DEFINED_FUNCTION_MAX_DIRS+1) //note:the +1 is for the PROGRAM_PREFIX or default directory==nc_files @@ -115,13 +117,15 @@ int emcTaskInit() int num,dct,dmax; char path[EMC_SYSTEM_CMD_LEN]; struct stat buf; - IniFile inifile; + IniFile inifile(emc_inifile); ZERO_EMC_POSE(emcStatus->task.toolOffset); - inifile.Open(emc_inifile); + if(!inifile) { + return -1; + } // Identify user_defined_function directories - if (auto inistring = inifile.Find("PROGRAM_PREFIX", "DISPLAY")) { + if (auto inistring = inifile.findString("PROGRAM_PREFIX", "DISPLAY")) { if (inistring->length() >= sizeof(mdir[0])) { rcs_print("[DISPLAY]PROGRAM_PREFIX too long (max len %zu)\n", sizeof(mdir[0])); return -1; @@ -135,7 +139,7 @@ int emcTaskInit() // user can specify a list of directories for user defined functions // with a colon (:) separated list - if (auto inistring = inifile.Find("USER_M_PATH", "RS274NGC")) { + if (auto inistring = inifile.findString("USER_M_PATH", "RS274NGC")) { char* nextdir; char tmpdirs[PATH_MAX]; @@ -163,26 +167,25 @@ int emcTaskInit() } dmax=dct; } - inifile.Close(); /* check for programs named programs/M100 .. programs/M199 and add any to the user defined functions list */ for (num = 0; num < USER_DEFINED_FUNCTION_NUM; num++) { for (dct=0; dct < dmax; dct++) { - char expanddir[LINELEN]; + std::string expanddir; if (!mdir[dct][0]) continue; - if (inifile.TildeExpansion(mdir[dct],expanddir,sizeof(expanddir))) { + if (inifile.TildeExpansion(mdir[dct],expanddir)) { rcs_print("emcTaskInit: TildeExpansion failed for %s, ignoring\n", mdir[dct]); } - size_t ret = snprintf(path, sizeof(path), "%s/M1%02d",expanddir,num); + size_t ret = snprintf(path, sizeof(path), "%s/M1%02d",expanddir.c_str(),num); if (ret < sizeof(path) && 0 == stat(path, &buf)) { if (buf.st_mode & S_IXUSR) { // set the user_defined_fmt string with dirname // note the %%02d means 2 digits after the M code // and we need two % to get the literal % ret = snprintf(user_defined_fmt[dct], sizeof(user_defined_fmt[dct]), - "%s/M1%%02d", expanddir); // update global + "%s/M1%%02d", expanddir.c_str()); // update global if(ret >= sizeof(user_defined_fmt[0])){ return -EMSGSIZE; // name truncated } else { @@ -430,17 +433,17 @@ static int waitFlag = 0; int emcTaskPlanInit() { if(!pinterp) { - IniFile inifile; - inifile.Open(emc_inifile); - if(auto inistring = inifile.Find("INTERPRETER", "TASK")) { - pinterp = interp_from_shlib(inistring->c_str()); - fprintf(stderr, "interp_from_shlib() -> %p\n", pinterp); - if (!pinterp) { - fprintf(stderr, "failed to load [TASK]INTERPRETER (%s)\n", inistring->c_str()); - return -1; + IniFile inifile(emc_inifile); + if(inifile) { + if(auto inistring = inifile.findString("INTERPRETER", "TASK")) { + pinterp = interp_from_shlib(inistring->c_str()); + fprintf(stderr, "interp_from_shlib() -> %p\n", pinterp); + if (!pinterp) { + fprintf(stderr, "failed to load [TASK]INTERPRETER (%s)\n", inistring->c_str()); + return -1; + } } - } - inifile.Close(); + } } if(!pinterp) { pinterp = new Interp; diff --git a/src/emc/task/emctaskmain.cc b/src/emc/task/emctaskmain.cc index 83fc1910434..1d709157425 100644 --- a/src/emc/task/emctaskmain.cc +++ b/src/emc/task/emctaskmain.cc @@ -73,7 +73,8 @@ fpu_control_t __fpu_control = _FPU_IEEE & ~(_FPU_MASK_IM | _FPU_MASK_ZM | _FPU_M #include "nml_intf/emc.hh" // EMC NML #include "nml_intf/emc_nml.hh" #include "nml_intf/canon.hh" // CANON_TOOL_TABLE stuff -#include "libnml/inifile/inifile.hh" // INIFILE +#include +#include "usr_intf/mapini.hh" #include "nml_intf/interpl.hh" // NML_INTERP_LIST, interp_list #include "nml_intf/emcglb.h" // EMC_INIFILE,NMLFILE, EMC_TASK_CYCLE_TIME #include "nml_intf/interp_return.hh" // public interpreter return values @@ -86,6 +87,8 @@ fpu_control_t __fpu_control = _FPU_IEEE & ~(_FPU_MASK_IM | _FPU_MASK_ZM | _FPU_M #include "motion/motion.h" // EMCMOT_ORIENT_* #include "ini/inihal.hh" +using namespace linuxcnc; + static emcmot_config_t emcmotConfig; /* time after which the user interface is declared dead @@ -121,8 +124,6 @@ static int emcTaskEager = 0; static int no_force_homing = 0; // forces the user to home first before allowing MDI and Program run //can be overridden by [TRAJ]NO_FORCE_HOMING=1 -static double EMC_TASK_CYCLE_TIME_ORIG = 0.0; - // delay counter static double taskExecDelayTimeout = 0.0; @@ -3095,55 +3096,23 @@ static int emctask_shutdown(void) static int iniLoad(const char *filename) { - IniFile inifile; - char version[LINELEN], machine[LINELEN]; - double saveDouble; - int saveInt; + IniFile inifile(filename); - // open it - if (inifile.Open(filename) == false) { - return -1; + if (!inifile) { + return -1; } - if (auto inistring = inifile.Find("JOINTS", "KINS")) { - // copy to global - if (1 != sscanf(inistring->c_str(), "%i", &joints)) { - joints = 0; - } - } else { - // not found, use default - joints = 0; - } + // FIXME: range limit [KINS]JOINTS + joints = inifile.findSIntV("JOINTS", "KINS", 0); // EMC debugging flags - emc_debug = 0; // disabled by default - if (auto inistring = inifile.Find("DEBUG", "EMC")) { - // parse to global - if (sscanf(inistring->c_str(), "%x", &emc_debug) < 1) { - perror("failed to parse [EMC] DEBUG"); - } - } + emc_debug = inifile.findUIntV("DEBUG", "EMC", 0); // set output for RCS messages - set_rcs_print_destination(RCS_PRINT_TO_STDOUT); // use stdout by default - if (auto inistring = inifile.Find("RCS_DEBUG_DEST", "EMC")) { - static RCS_PRINT_DESTINATION_TYPE type; - if (*inistring == "STDOUT") { - type = RCS_PRINT_TO_STDOUT; - } else if (*inistring == "STDERR") { - type = RCS_PRINT_TO_STDERR; - } else if (*inistring == "FILE") { - type = RCS_PRINT_TO_FILE; - } else if (*inistring == "LOGGER") { - type = RCS_PRINT_TO_LOGGER; - } else if (*inistring == "MSGBOX") { - type = RCS_PRINT_TO_MESSAGE_BOX; - } else if (*inistring == "NULL") { - type = RCS_PRINT_TO_NULL; - } else { - type = RCS_PRINT_TO_STDOUT; - } - set_rcs_print_destination(type); + if (auto inival = mapRcsDestination(inifile, "RCS_DEBUG_DEST", "EMC")) { + set_rcs_print_destination(*inival); + } else { + set_rcs_print_destination(RCS_PRINT_TO_STDOUT); } // NML/RCS debugging flags @@ -3155,118 +3124,64 @@ static int iniLoad(const char *filename) } // set flags if RCS_DEBUG in ini file - if (auto inistring = inifile.Find("RCS_DEBUG", "EMC")) { - long unsigned int flags; - if (sscanf(inistring->c_str(), "%lx", &flags) < 1) { - perror("failed to parse [EMC] RCS_DEBUG"); - } + if (auto inival = inifile.findUInt("RCS_DEBUG", "EMC")) { // clear all flags clear_rcs_print_flag(PRINT_EVERYTHING); // set parsed flags - set_rcs_print_flag((long)flags); + set_rcs_print_flag((long)*inival); } // output infinite RCS errors by default - max_rcs_errors_to_print = -1; - if (auto inistring = inifile.Find("RCS_MAX_ERR", "EMC")) { - if (sscanf(inistring->c_str(), "%d", &max_rcs_errors_to_print) < 1) { - perror("failed to parse [EMC] RCS_MAX_ERR"); - } - } + max_rcs_errors_to_print = inifile.findSIntV("RCS_MAX_ERR", "EMC", -1); - if (emc_debug & EMC_DEBUG_CONFIG) { - auto ver = inifile.Find("VERSION", "EMC"); - rtapi_strlcpy(version, ver ? ver->c_str() : "unknown", LINELEN-1); - - auto mach = inifile.Find("MACHINE", "EMC"); - rtapi_strlcpy(machine, mach ? mach->c_str() : "unknown", LINELEN-1); - extern char *program_invocation_short_name; - rcs_print( - "%s (%d) task: machine '%s' version '%s'\n", - program_invocation_short_name, getpid(), machine, version - ); - } + if (emc_debug & EMC_DEBUG_CONFIG) { + std::string version = inifile.findStringV("VERSION", "EMC", ""); + std::string machine = inifile.findStringV("MACHINE", "EMC", ""); + extern char *program_invocation_short_name; + rcs_print( + "%s (%d) task: machine '%s' version '%s'\n", + program_invocation_short_name, getpid(), machine.c_str(), version.c_str() + ); + } - if (auto inistring = inifile.Find("NML_FILE", "EMC")) { + if (auto inistring = inifile.findString("NML_FILE", "EMC")) { // copy to global rtapi_strxcpy(emc_nmlfile, inistring->c_str()); - } else { - // not found, use default - } + } // else not found, use default - saveInt = emc_task_interp_max_len; //remember default or previously set value - if (auto inistring = inifile.Find("INTERP_MAX_LEN", "TASK")) { - if (1 == sscanf(inistring->c_str(), "%d", &emc_task_interp_max_len)) { - if (emc_task_interp_max_len <= 0) { - emc_task_interp_max_len = saveInt; - } - } else { - emc_task_interp_max_len = saveInt; - } + if (auto inival = inifile.findSInt("INTERP_MAX_LEN", "TASK")) { + if (*inival > 0) { + emc_task_interp_max_len = *inival; + } } - if (auto inistring = inifile.Find("RS274NGC_STARTUP_CODE", "RS274NGC")) { + if (auto inistring = inifile.findString("RS274NGC_STARTUP_CODE", "RS274NGC")) { // copy to global rtapi_strxcpy(rs274ngc_startup_code, inistring->c_str()); } - saveDouble = emc_task_cycle_time; - EMC_TASK_CYCLE_TIME_ORIG = emc_task_cycle_time; emcTaskNoDelay = 0; - if (auto inistring = inifile.Find("CYCLE_TIME", "TASK")) { - if (1 == sscanf(inistring->c_str(), "%lf", &emc_task_cycle_time)) { - // found it - // if it's <= 0.0, then flag that we don't want to - // wait at all, which will set the EMC_TASK_CYCLE_TIME - // global to the actual time deltas - if (emc_task_cycle_time <= 0.0) { - emcTaskNoDelay = 1; - } - } else { - // found, but invalid - emc_task_cycle_time = saveDouble; - rcs_print - ("invalid [TASK] CYCLE_TIME in %s (%s); using default %f\n", - filename, inistring->c_str(), emc_task_cycle_time); - } + if (auto inival = inifile.findReal("CYCLE_TIME", "TASK")) { + emc_task_cycle_time = *inival; + if (*inival <= 0.0) { + emcTaskNoDelay = 1; + } } else { - // not found, using default - rcs_print("[TASK] CYCLE_TIME not found in %s; using default %f\n", - filename, emc_task_cycle_time); + // not found, using default + rcs_print("[TASK] CYCLE_TIME not found in %s; using default %f\n", filename, emc_task_cycle_time); } - - if (auto inistring = inifile.Find("NO_FORCE_HOMING", "TRAJ")) { - if (1 == sscanf(inistring->c_str(), "%d", &no_force_homing)) { - // found it - // if it's <= 0.0, then set it 0 so that homing is required before MDI or Auto - if (no_force_homing <= 0) { - no_force_homing = 0; - } - } else { - // found, but invalid - no_force_homing = 0; - rcs_print - ("invalid [TRAJ] NO_FORCE_HOMING in %s (%s); using default %d\n", - filename, inistring->c_str(), no_force_homing); - } - } else { - // not found, using default - no_force_homing = 0; - } + no_force_homing = inifile.findBoolV("NO_FORCE_HOMING", "TRAJ", false); // configurable template for iocontrol reason display - if (auto inistring = inifile.Find("IO_ERROR", "TASK")) { + if (auto inistring = inifile.findString("IO_ERROR", "TASK")) { io_error = strdup(inistring->c_str()); } // max number of queued MDI commands - if (auto inistring = inifile.Find("MDI_QUEUED_COMMANDS", "TASK")) { - max_mdi_queued_commands = atoi(inistring->c_str()); + if (auto inival = inifile.findSInt("MDI_QUEUED_COMMANDS", "TASK")) { + max_mdi_queued_commands = *inival; } - // close it - inifile.Close(); - return 0; } diff --git a/src/emc/task/taskclass.cc b/src/emc/task/taskclass.cc index ca7bb3b62cd..3bc6b4fe21e 100644 --- a/src/emc/task/taskclass.cc +++ b/src/emc/task/taskclass.cc @@ -28,6 +28,8 @@ #include "taskclass.hh" #include +using namespace linuxcnc; + /******************************************************************** * * Description: iocontrol_hal_init(void) @@ -133,14 +135,16 @@ Task::Task(EMC_IO_STAT & emcioStatus_in) : tool_status(0) { - IniFile inifile; + IniFile inifile(ini_filename); - if (inifile.Open(ini_filename)) { - inifile.Find(&random_toolchanger, "RANDOM_TOOLCHANGER", "EMCIO"); - if (auto t = inifile.Find("TOOL_TABLE", "EMCIO")) + if (inifile) { + if (auto inival = inifile.findBool("RANDOM_TOOLCHANGER", "EMCIO")) { + random_toolchanger = *inival; + } + if (auto t = inifile.findString("TOOL_TABLE", "EMCIO")) tooltable_filename = strdup(t->c_str()); - if (auto t = inifile.Find("DB_PROGRAM", "EMCIO")) { + if (auto t = inifile.findString("DB_PROGRAM", "EMCIO")) { db_mode = tooldb_t::DB_ACTIVE; tooldata_set_db(db_mode); strncpy(db_program, t->c_str(), LINELEN - 1); @@ -150,7 +154,6 @@ Task::Task(EMC_IO_STAT & emcioStatus_in) : fprintf(stderr,"DB_PROGRAM active: IGNORING tool table file %s\n", tooltable_filename); } - inifile.Close(); } #ifdef TOOL_NML //{ @@ -201,12 +204,13 @@ Task::Task(EMC_IO_STAT & emcioStatus_in) : Task::~Task() {}; // set the have_tool_change_position global -static int readToolChange(IniFile *toolInifile) +static int readToolChange(const IniFile &toolInifile) { int retval = 0; - auto inistring = toolInifile->Find("TOOL_CHANGE_POSITION", "EMCIO"); + auto inistring = toolInifile.findString("TOOL_CHANGE_POSITION", "EMCIO"); if (inistring) { + // FIXME: This should really be a LCNC library call written in C++ /* found an entry */ if (9 == sscanf(inistring->c_str(), "%lf %lf %lf %lf %lf %lf %lf %lf %lf", &tool_change_position.tran.x, @@ -261,17 +265,15 @@ static int readToolChange(IniFile *toolInifile) static int iniTool(const char *filename) { int retval = 0; - IniFile toolInifile; + IniFile toolInifile(filename); - if (toolInifile.Open(filename) == false) { + if (!toolInifile) { return -1; } // read the tool change positions - if (0 != readToolChange(&toolInifile)) { + if (0 != readToolChange(toolInifile)) { retval = -1; } - // close the inifile - toolInifile.Close(); return retval; } diff --git a/src/emc/task/taskclass.hh b/src/emc/task/taskclass.hh index 6fd916146ed..b19c0a00aae 100644 --- a/src/emc/task/taskclass.hh +++ b/src/emc/task/taskclass.hh @@ -19,7 +19,7 @@ #define TASKCLASS_HH #include "nml_intf/emc.hh" -#include "libnml/inifile/inifile.hh" +#include #include "../../hal/hal.hh" #include "tooldata/tooldata.hh" diff --git a/src/emc/task/taskintf.cc b/src/emc/task/taskintf.cc index 61bcdf56c06..7aaee75fe73 100644 --- a/src/emc/task/taskintf.cc +++ b/src/emc/task/taskintf.cc @@ -27,13 +27,15 @@ #include "nml_intf/emc_nml.hh" #include "libnml/rcs/rcs_print.hh" #include "libnml/os_intf/timer.hh" -#include "libnml/inifile/inifile.hh" +#include #include "ini/iniaxis.hh" #include "ini/inijoint.hh" #include "ini/inispindle.hh" #include "ini/initraj.hh" #include "ini/inihal.hh" +using namespace linuxcnc; + value_inihal_data old_inihal_data; /* define this to catch isnan errors, for rtlinux FPU register @@ -1729,10 +1731,11 @@ int emcTrajUpdate(EMC_TRAJ_STAT * stat) int emcPositionLoad() { double positions[EMCMOT_MAX_JOINTS]; - IniFile ini; - ini.Open(emc_inifile); - auto posfile = ini.Find("POSITION_FILE", "TRAJ"); - ini.Close(); + IniFile ini(emc_inifile); + if(!ini) { + return -1; + } + auto posfile = ini.findString("POSITION_FILE", "TRAJ"); if(!posfile || posfile->empty()) return 0; FILE *f = fopen(posfile->c_str(), "r"); if(!f) return 0; @@ -1757,17 +1760,11 @@ int emcPositionLoad() { int emcPositionSave() { - IniFile ini; - - ini.Open(emc_inifile); - std::optional posfile; - try { - posfile = ini.Find("POSITION_FILE", "TRAJ"); - } catch (IniFile::Exception e) { - ini.Close(); + IniFile ini(emc_inifile); + if(!ini) { return -1; } - ini.Close(); + auto posfile = ini.findString("POSITION_FILE", "TRAJ"); if(!posfile || posfile->empty()) return 0; // like the var file, make sure the posfile is recreated according to umask diff --git a/src/emc/usr_intf/Submakefile b/src/emc/usr_intf/Submakefile index 9a0c0bcac18..b914e30ffe1 100644 --- a/src/emc/usr_intf/Submakefile +++ b/src/emc/usr_intf/Submakefile @@ -1,41 +1,46 @@ EMCSHSRCS := emc/usr_intf/emcsh.cc \ + emc/usr_intf/mapini.cc \ emc/usr_intf/shcom.cc EMCRSHSRCS := emc/usr_intf/emcrsh.cc \ + emc/usr_intf/mapini.cc \ emc/usr_intf/shcom.cc EMCSCHEDSRCS := emc/usr_intf/schedrmt.cc \ emc/usr_intf/emcsched.cc \ + emc/usr_intf/mapini.cc \ emc/usr_intf/shcom.cc EMCLCDSRCS := emc/usr_intf/emclcd.cc \ + emc/usr_intf/mapini.cc \ emc/usr_intf/shcom.cc \ emc/usr_intf/sockets.c -HALUISRCS := emc/usr_intf/halui.cc +HALUISRCS := emc/usr_intf/halui.cc \ + emc/usr_intf/mapini.cc USERSRCS += $(EMCSHSRCS) $(EMCRSHSRCS) $(EMCSCHEDSRCS) $(EMCLCDSRCS) $(USRMOTSRCS) $(HALUISRCS) $(call TOOBJSDEPS, $(EMCSHSRCS)) : EXTRAFLAGS = $(ULFLAGS) $(TCL_CFLAGS) -fPIC -../tcl/linuxcnc.so: $(call TOOBJS, $(EMCSHSRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.0 ../lib/libnml.so.0 +../tcl/linuxcnc.so: $(call TOOBJS, $(EMCSHSRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.1 ../lib/libnml.so.0 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $(ULFLAGS) $(TCL_CFLAGS) $^ $(TCL_LIBS) -lXinerama TARGETS += ../tcl/linuxcnc.so -../bin/linuxcncrsh: $(call TOOBJS, $(EMCRSHSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.0 +../bin/linuxcncrsh: $(call TOOBJS, $(EMCRSHSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -o $@ $(ULFLAGS) $^ -lfmt $(LDFLAGS) TARGETS += ../bin/linuxcncrsh -../bin/schedrmt: $(call TOOBJS, $(EMCSCHEDSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.0 +../bin/schedrmt: $(call TOOBJS, $(EMCSCHEDSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) -o $@ $(ULFLAGS) $^ -lpthread $(LDFLAGS) + $(Q)$(CXX) -o $@ $(ULFLAGS) $^ -lpthread -lfmt $(LDFLAGS) TARGETS += ../bin/schedrmt -../bin/linuxcnclcd: $(call TOOBJS, $(EMCLCDSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.0 +../bin/linuxcnclcd: $(call TOOBJS, $(EMCLCDSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcnc.a ../lib/libnml.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) -o $@ $(ULFLAGS) $^ $(LDFLAGS) + $(Q)$(CXX) -o $@ $(ULFLAGS) $^ -lfmt $(LDFLAGS) TARGETS += ../bin/linuxcnclcd -../bin/halui: $(call TOOBJS, $(HALUISRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.0 ../lib/libnml.so.0 ../lib/liblinuxcnchal.so.0 ../lib/libtooldata.so.0 +../bin/halui: $(call TOOBJS, $(HALUISRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.1 ../lib/libnml.so.0 ../lib/liblinuxcnchal.so.0 ../lib/libtooldata.so.0 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) $(CXXFLAGS) -o $@ $(ULFLAGS) $^ $(LDFLAGS) TARGETS += ../bin/halui diff --git a/src/emc/usr_intf/axis/extensions/emcmodule.cc b/src/emc/usr_intf/axis/extensions/emcmodule.cc index 4b17a84f927..e63d9b83c6f 100644 --- a/src/emc/usr_intf/axis/extensions/emcmodule.cc +++ b/src/emc/usr_intf/axis/extensions/emcmodule.cc @@ -1,6 +1,7 @@ // This is a component of AXIS, a front-end for LinuxCNC // Copyright 2004, 2005, 2006 Jeff Epler and // Chris Radek +// Copyright 2026 B.Stultiens // // 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 @@ -17,23 +18,20 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #define PY_SSIZE_T_CLEAN -#define __STDC_FORMAT_MACROS #include #include #include #include -#include #include "config.h" #include "libnml/rcs/rcs.hh" #include "nml_intf/emc.hh" #include "nml_intf/emc_nml.hh" #include #include "config.h" -#include "libnml/inifile/inifile.hh" +#include #include "libnml/os_intf/timer.hh" #include "libnml/nml/nml_oi.hh" #include "libnml/rcs/rcs_print.hh" -#include #include #include @@ -45,6 +43,8 @@ #include #include +using namespace linuxcnc; + #define LOCAL_SPINDLE_FORWARD (1) #define LOCAL_SPINDLE_REVERSE (-1) #define LOCAL_SPINDLE_OFF (0) @@ -72,14 +72,9 @@ #define LOCAL_AUTO_REVERSE (4) #define LOCAL_AUTO_FORWARD (5) -/* This definition of offsetof avoids the g++ warning - * 'invalid offsetof from non-POD type'. - */ -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - struct pyIniFile { PyObject_HEAD - IniFile *i; + std::string inifile; }; struct pyStatChannel { @@ -106,57 +101,530 @@ static int Ini_init(pyIniFile *self, PyObject *a, PyObject * /*k*/) { char *inifile; if(!PyArg_ParseTuple(a, "s", &inifile)) return -1; - if(!self->i) - self->i = new IniFile(); + self->inifile = inifile; - if (!self->i->Open(inifile)) { + IniFile ini(inifile); // Test open (will parse and cache the file) + if (!ini) { PyErr_Format( error, "inifile.open(%s) failed", inifile); return -1; } return 0; } -static PyObject *Ini_find(pyIniFile *self, PyObject *args) { - const char *s1, *s2; +// +// PyBool linuxcnc.ini.hasvariable(string:section, string:variable) +// +// Find [section]variable and return true if found. The 'section' may be an +// empty string and the first occurrence of 'variable' in any section is +// searched. +// +static PyObject *Ini_has_variable(pyIniFile *self, PyObject *args) +{ + const char *sect = "", *var; int num = 1; - if(!PyArg_ParseTuple(args, "ss|i:find", &s1, &s2, &num)) return NULL; + if(!PyArg_ParseTuple(args, "s|si:hasvariable", §, &var, &num)) + return NULL; - if (auto out = self->i->Find(s2, s1, num)) - return PyUnicode_FromString(out.value().c_str()); + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } - Py_INCREF(Py_None); - return Py_None; + return PyBool_FromLong((long)ini.hasVariable(num, var, sect)); +} + +// +// PyBool linuxcnc.ini.hassection(string:section) +// +// Find [section] and return true if found. +// +static PyObject *Ini_has_section(pyIniFile *self, PyObject *args) +{ + const char *sect; + if(!PyArg_ParseTuple(args, "s:hassection", §)) + return NULL; + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + return PyBool_FromLong((long)ini.hasSection(sect)); +} + +// +// The prototype of PyArg_ParseTupleAndKeywords() was originally using char** +// for the keyword array. This is obviously problematic and was changed in 3.13 +// to const. C++ will generate an error when demoting const to non-const +// pointers. We need to support multiple python versions and make this +// patchwork to support them all. +#if PY_VERSION_HEX >= 0x030d00f0 // 3.13 +#define PARSE_KW_CONST const +#else +#define PARSE_KW_CONST +#endif + +static PARSE_KW_CONST char kw_empty[] = ""; +static PARSE_KW_CONST char kw_fallback[] = "fallback"; +static PARSE_KW_CONST char *kw_eefn[] = { kw_empty, kw_empty, kw_empty, kw_fallback, NULL }; + +#undef PARSE_KW_CONST + +// +// PyBool|None linuxcnc.ini.getbool(string:section, string:variable [, int:num] [, fallback=val]) +// +// Find [section]variable and convert it to boolean. Valid boolean values are +// (case insensitive) {true, yes, on, 1} and {false, no, off, 0}. +// The optional named argument fallback= defines the object to return when the +// variable is not found or invalid. The optional named option num= selects the +// num'th variable of the section (default to 1). +// +static PyObject *Ini_get_bool(pyIniFile *self, PyObject *args, PyObject *kwargs) +{ + const char *sect, *var; + int num = 1; + PyObject *def = Py_None; + if(!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|i$O:getbool", kw_eefn, §, &var, &num, &def)) + return NULL; + + if(num < 1) { + PyErr_Format(error, "Argument 'num' must be >= 1"); + return NULL; + } + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + if(auto v = ini.findBool(num, var, sect)) + return PyBool_FromLong(*v); + + // Not found or error + // We have a fallback set by argument or as None + Py_INCREF(def); + return def; +} + +// +// PyLong|None linuxcnc.ini.getsint(string:section, string:variable [, int:num] [, fallback=val]) +// +// Find [section]variable and convert it to a signed integer. +// The optional named argument fallback= defines the value to return when the +// variable is not found or invalid. The optional named option num= selects the +// num'th variable of the section (default to 1). +// +static PyObject *Ini_get_sint(pyIniFile *self, PyObject *args, PyObject *kwargs) +{ + const char *sect, *var; + int num = 1; + PyObject *def = Py_None; + if(!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|i$O:getsint", kw_eefn, §, &var, &num, &def)) + return NULL; + + if(num < 1) { + PyErr_Format(error, "Argument 'num' must be >= 1"); + return NULL; + } + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + if(auto v = ini.findSInt(num, var, sect)) + return PyLong_FromLongLong(*v); + + // Not found or error + // We have a fallback set by argument or as None + Py_INCREF(def); + return def; +} + +// +// PyLong|None linuxcnc.ini.getuint(string:section, string:variable [, int:num] [, fallback=val]) +// +// Find [section]variable and convert it to an unsigned integer. +// The optional named argument fallback= defines the value to return when the +// variable is not found or invalid. The optional named option num= selects the +// num'th variable of the section (default to 1). +// +static PyObject *Ini_get_uint(pyIniFile *self, PyObject *args, PyObject *kwargs) +{ + const char *sect, *var; + int num = 1; + PyObject *def = Py_None; + if(!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|i$O:getuint", kw_eefn, §, &var, &num, &def)) + return NULL; + + if(num < 1) { + PyErr_Format(error, "Argument 'num' must be >= 1"); + return NULL; + } + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + if(auto v = ini.findUInt(num, var, sect)) + return PyLong_FromUnsignedLongLong(*v); + + // Not found or error + // We have a fallback set by argument or as None + Py_INCREF(def); + return def; +} + +// +// PyFloat|None linuxcnc.ini.getreal(string:section, string:variable [, int:num] [, fallback=val]) +// +// Find [section]variable and convert it to an unsigned integer. +// The optional named argument fallback= defines the value to return when the +// variable is not found or invalid. The optional named option num= selects the +// num'th variable of the section (default to 1). +// +static PyObject *Ini_get_real(pyIniFile *self, PyObject *args, PyObject *kwargs) +{ + const char *sect, *var; + int num = 1; + PyObject *def = Py_None; + if(!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|i$O:getreal", kw_eefn, §, &var, &num, &def)) + return NULL; + + if(num < 1) { + PyErr_Format(error, "Argument 'num' must be >= 1"); + return NULL; + } + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + if(auto v = ini.findReal(num, var, sect)) + return PyFloat_FromDouble(*v); + + // Not found or error + // We have a fallback set by argument or as None + Py_INCREF(def); + return def; +} + +// +// PyString|None linuxcnc.ini.getstring(string:section, string:variable [, int:num] [, fallback=val]) +// +// Find [section]variable and convert it to a string. +// The optional named argument fallback= defines the value to return when the +// variable is not found or invalid. The optional named option num= selects the +// num'th variable of the section (default to 1). +// +static PyObject *Ini_get_string(pyIniFile *self, PyObject *args, PyObject *kwargs) +{ + const char *sect, *var; + int num = 1; + PyObject *def = Py_None; + if(!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|i$O:getstring", kw_eefn, §, &var, &num, &def)) + return NULL; + + if(num < 1) { + PyErr_Format(error, "Argument 'num' must be >= 1"); + return NULL; + } + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + if(auto v = ini.findString(num, var, sect)) + return PyUnicode_FromString(v->c_str()); + + // Not found or error + // We have a fallback set by argument or as None + Py_INCREF(def); + return def; +} + +// +// PyList(PyTuple(variable,value)) linuxcnc.ini.getsection([string:section]) +// +// Returns a list of tuples containing variable name and value from the named +// section or all variables from all sections if no section given. +// +static PyObject *Ini_get_variables(pyIniFile *self, PyObject *args) +{ + const char *sect = ""; + if(!PyArg_ParseTuple(args, "|s:getvariables", §)) return NULL; + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + PyObject *list = PyList_New(0); + if(!list) + return NULL; + + for(auto const &v : ini.findVariables(sect)) { + PyObject *var = PyUnicode_FromString(v.first.c_str()); + if(!var) { + Py_DECREF(list); + return NULL; + } + PyObject *val = PyUnicode_FromString(v.second.c_str()); + if(!val) { + Py_DECREF(var); + Py_DECREF(list); + return NULL; + } + PyObject *tup = PyTuple_New(2); + if(!tup) { + Py_DECREF(val); + Py_DECREF(var); + Py_DECREF(list); + return NULL; + } + PyTuple_SET_ITEM(tup, 0, var); + PyTuple_SET_ITEM(tup, 1, val); + PyList_Append(list, tup); + } + return list; +} + +// +// PyList linuxcnc.ini.sections() +// +// Returns a list of section names from the ini-file. +// +static PyObject *Ini_get_sections(pyIniFile *self, PyObject *) +{ + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + PyObject *list = PyList_New(0); + if(!list) + return NULL; + + for(auto const &v : ini.findSections()) { + PyObject *val = PyUnicode_FromString(v.c_str()); + if(!val) { + Py_DECREF(list); + return NULL; + } + PyList_Append(list, val); + } + return list; } +// +// PyList linuxcnc.ini.findall(string:section [, string:variable]) +// +// Return a list of [section]variable entries where the variable name is the +// same for all entries found. If the section string is empty (''), then all +// variables of that name are returned. +// static PyObject *Ini_findall(pyIniFile *self, PyObject *args) { - const char *s1, *s2; + const char *sect; + const char *var = ""; int num = 1; - if(!PyArg_ParseTuple(args, "ss:findall", &s1, &s2)) return NULL; + if(!PyArg_ParseTuple(args, "s|s:findall", §, &var)) return NULL; + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } PyObject *result = PyList_New(0); - while(auto out = self->i->Find(s2, s1, num)) { + if(!result) + return NULL; + + while(auto const &out = ini.findString(num, var, sect)) { PyList_Append(result, PyUnicode_FromString(out.value().c_str())); num++; } return result; } +// +// PyTuple(filename, lineno) linuxcnc.ini.lineof(string:section, string:variable [, int:num]) +// +// Return a (filname,linenr) tuple containing the filename and line number of +// the [section]variable. Optionally you can request the num'th instance of the +// variable. +// +static PyObject *Ini_lineof(pyIniFile *self, PyObject *args) { + const char *sect; + const char *var = ""; + int num = 1; + if(!PyArg_ParseTuple(args, "ss|i:lineof", §, &var, &num)) return NULL; + + if(num < 1) { + PyErr_Format(error, "Argument 'num' must be >= 1"); + return NULL; + } + + IniFile ini(self->inifile); + if(!ini) { + PyErr_Format(error, "Internal: ini-file could not be reopened"); + return NULL; + } + + PyObject *result = PyTuple_New(2); + if(!result) + return NULL; + + auto v = ini.lineOf(num, var, sect); + if(v.second < 0) { + // Set to (None, None) + PyTuple_SET_ITEM(result, 0, Py_None); + PyTuple_SET_ITEM(result, 1, Py_None); + } else { + PyObject *fname = PyUnicode_FromString(v.first.c_str()); + if(!fname) { + Py_DECREF(result); + return NULL; + } + PyObject *lineno = PyLong_FromLong(v.second); + if(!lineno) { + Py_DECREF(fname); + Py_DECREF(result); + return NULL; + } + PyTuple_SET_ITEM(result, 0, fname); + PyTuple_SET_ITEM(result, 1, lineno); + } + return result; +} + static void Ini_dealloc(pyIniFile *self) { - if (self->i) - self->i->Close(); - delete self->i; PyObject_Del(self); } +// +// This #pragma sucks... +// In C++ casting PyCFunctionWithKeywords to PyCFunction results in a warning +// about 'cast between incompatible function types'. However, this is the way +// it is done interfacing to the C API of Python. +// We disable the diagnostic warning temporarily here so we are not bothered. +// The alternative, creating an overlapping PyMethodDef structure with a +// unionized ml_meth field with all possible function signatures, is possible +// but not Python version secure. +// +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" static PyMethodDef Ini_methods[] = { - {"find", (PyCFunction)Ini_find, METH_VARARGS, - "Find value in inifile as string. This uses the ConfigParser-style " - "(section,option) order, not the linuxcnc order."}, + {"hassection", (PyCFunction)Ini_has_section, METH_VARARGS, + "PyBool hassection(section)\n" + "Returns a boolean indicating whether or not the given section was found " + "in the ini-file." }, + {"hasvariable", (PyCFunction)Ini_has_variable, METH_VARARGS, + "PyBool hasvariable(section, variable)\n" + "Returns a boolean indicating whether or not the given [section]variable " + "was found in the ini-file. The first occurrence of the variable name will " + "be searched if the section name is empty." }, + {"getbool", (PyCFunction)Ini_get_bool, METH_VARARGS|METH_KEYWORDS, + "PyBool|None getbool(section, variable [, num] [, fallback=])\n" + "Returns the value of the variable converted to boolean if it was a valid " + "boolean. The return value is None if the variable was not found or an " + "invalid boolean value was detected and no fallback= was provided. The " + "optional num argument may be used to select the num'th variable of that " + "name in the section." }, + {"getsint", (PyCFunction)Ini_get_sint, METH_VARARGS|METH_KEYWORDS, + "PyInt|None getsint(section, variable [, num] [, fallback=])\n" + "Returns the value of the variable converted to signed integer if it was a " + "valid integer. The return value is None if the variable was not found or " + "an invalid value was detected and no fallback= was provided. The optional " + "num argument may be used to select the num'th variable of that name in " + "the section." }, + {"getint", (PyCFunction)Ini_get_sint, METH_VARARGS|METH_KEYWORDS, + "PyInt|None getint(section, variable [, num] [, fallback=])\n" + "Alias of getsint" }, + {"getuint", (PyCFunction)Ini_get_uint, METH_VARARGS|METH_KEYWORDS, + "PyInt|None getuint(section, variable [, num] [, fallback=])\n" + "Returns the value of the variable converted to unsigned integer if it was " + "a valid unsigned integer. The return value is None if the variable was " + "not found or an invalid value was detected and no fallback= was provided. " + "The optional num argument may be used to select the num'th variable of " + "that name in the section." }, + {"getreal", (PyCFunction)Ini_get_real, METH_VARARGS|METH_KEYWORDS, + "PyFloat|None getreal(section, variable [, num] [, fallback=])\n" + "Returns the value of the variable converted to floating point real if it " + "was a valid real. The return value is None if the variable was not found " + "or an invalid value was detected and no fallback= was provided. The " + "optional num argument may be used to select the num'th variable of that " + "name in the section." }, + {"getfloat", (PyCFunction)Ini_get_real, METH_VARARGS|METH_KEYWORDS, + "PyFloat|None getfloat(section, variable [, num] [, fallback=])\n" + "Alias of getreal()." }, + {"getstring", (PyCFunction)Ini_get_string, METH_VARARGS|METH_KEYWORDS, + "PyString|None getstring(section, variable [, num] [, fallback=])\n" + "Returns the value of the variable as a string if it exists. " + "The return value is None if the variable was not found and no fallback= " + "was provided. The optional num argument may be used to select the num'th " + "variable of that name in the section." }, + {"getsections", (PyCFunction)Ini_get_sections, METH_VARARGS, + "PyList getsections()\n" + "Returns a list of section names. " }, + {"getvariables", (PyCFunction)Ini_get_variables, METH_VARARGS, + "PyList(PyTuple(name,value)) getvariables([section])\n" + "Returns a list of (name,value) tuples of all variables in the named " + "section or the variables from all sections if the section name is not " + "specified." }, + {"find", (PyCFunction)Ini_get_string, METH_VARARGS|METH_KEYWORDS, + "PyString|None find(section, variable [, num] [, fallback=])\n" + "Alias of getstring()" }, {"findall", (PyCFunction)Ini_findall, METH_VARARGS, - "Find value in inifile as a list. This uses the ConfigParser-style " - "(section,option) order, not the linuxcnc order."}, + "PyList findall(section [,variable])\n" + "Find value(s) from named section in inifile as a list matching the " + "variable name." }, + {"lineof", (PyCFunction)Ini_lineof, METH_VARARGS, + "PyTuple(filename,lineno) lineof(section, variable [, num])\n" + "Returns a tuple with the filename and line number of the num'th " + "variable in the section. The first matching section variable is " + "returned if num if not provided. The tuple (None, None) is returned " + "if the variable is not found." }, {} }; +#pragma GCC diagnostic pop + +static const char linuxcncinidoc[] = + "LinuxCNC INI-file reader, parser and query module.\n" + "Instantiate with:\n" + " cfg = linuxcnc.ini('path_to_ini_file.ini')\n" + "\n" + "Available methods:\n" + " PyBool hassection(section)\n" + " PyBool hasvariable(section, variable)\n" + " PyBool|None getbool(section, variable [, num] [, fallback=])\n" + " PyInt|None getsint(section, variable [, num] [, fallback=])\n" + " PyInt|None getuint(section, variable [, num] [, fallback=])\n" + " PyFloat|None getreal(section, variable [, num] [, fallback=])\n" + " PyString|None getstring(section, variable [, num] [, fallback=])\n" + " PyList getsections()\n" + " PyList(PyTuple(name,value)) getvariables([section])\n" + " PyList findall(section [,variable])\n" + " PyTuple(filename,lineno) lineof(section, variable [, num])\n" + "\n" + "Several convenience methods are provided as aliases to above methods:\n" + " PyInt|None getint(section, variable [, num] [, fallback=])\n" + " PyFloat|None getfloat(section, variable [, num] [, fallback=])\n" + " PyString|None find(section, variable [, num] [, fallback=])\n" + "\n" + "Method documentation is provided with each method.\n" + ; static PyTypeObject Ini_Type = { PyVarObject_HEAD_INIT(NULL, 0) @@ -180,7 +648,7 @@ static PyTypeObject Ini_Type = { 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ + linuxcncinidoc, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ @@ -284,7 +752,7 @@ static int Stat_init(pyStatChannel *self, PyObject * /*a*/, PyObject * /*k*/) { } static void Stat_dealloc(PyObject *self) { - delete ((pyStatChannel*)self)->c; + delete reinterpret_cast(self)->c; PyObject_Del(self); } @@ -415,77 +883,86 @@ static PyMethodDef Stat_methods[] = { {} }; +/* This definition of offsetof avoids the g++ warning + * 'invalid offsetof from non-POD type'. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + #define O(x) offsetof(pyStatChannel,status.x) static PyMemberDef Stat_members[] = { // stat - {(char*)"echo_serial_number", T_INT, O(echo_serial_number), READONLY, NULL}, - {(char*)"state", T_INT, O(status), READONLY, NULL}, + { "echo_serial_number", T_INT, O(echo_serial_number), READONLY, NULL}, + { "state", T_INT, O(status), READONLY, NULL}, // task - {(char*)"task_mode", T_INT, O(task.mode), READONLY, NULL}, - {(char*)"task_state", T_INT, O(task.state), READONLY, + { "task_mode", T_INT, O(task.mode), READONLY, NULL}, + { "task_state", T_INT, O(task.state), READONLY, "Current Task state. Possible values:\n" " STATE_ESTOP: E-Stop is active.\n" " STATE_ESTOP_RESET: E-Stop is reset (cleared) but machine is off.\n" " STATE_OFF: Same as STATE_ESTOP_RESET, this one is not used.\n" " STATE_ON: Machine is out of E-Stop and is powered on.\n" }, - {(char*)"exec_state", T_INT, O(task.execState), READONLY, NULL}, - {(char*)"interp_state", T_INT, O(task.interpState), READONLY, NULL}, - {(char*)"call_level", T_INT, O(task.callLevel), READONLY, NULL}, - {(char*)"read_line", T_INT, O(task.readLine), READONLY, NULL}, - {(char*)"motion_line", T_INT, O(task.motionLine), READONLY, NULL}, - {(char*)"current_line", T_INT, O(task.currentLine), READONLY, NULL}, - {(char*)"file", T_STRING_INPLACE, O(task.file), READONLY, NULL}, - {(char*)"command", T_STRING_INPLACE, O(task.command), READONLY, NULL}, - {(char*)"program_units", T_INT, O(task.programUnits), READONLY, NULL}, - {(char*)"interpreter_errcode", T_INT, O(task.interpreter_errcode), READONLY, NULL}, - {(char*)"optional_stop", T_BOOL, O(task.optional_stop_state), READONLY, NULL}, - {(char*)"block_delete", T_BOOL, O(task.block_delete_state), READONLY, NULL}, - {(char*)"task_paused", T_INT, O(task.task_paused), READONLY, NULL}, - {(char*)"input_timeout", T_BOOL, O(task.input_timeout), READONLY, NULL}, - {(char*)"rotation_xy", T_DOUBLE, O(task.rotation_xy), READONLY, NULL}, - {(char*)"ini_filename", T_STRING_INPLACE, O(task.ini_filename), READONLY, NULL}, - {(char*)"delay_left", T_DOUBLE, O(task.delayLeft), READONLY, NULL}, - {(char*)"queued_mdi_commands", T_INT, O(task.queuedMDIcommands), READONLY, (char*)"Number of MDI commands queued waiting to run." }, + { "exec_state", T_INT, O(task.execState), READONLY, NULL}, + { "interp_state", T_INT, O(task.interpState), READONLY, NULL}, + { "call_level", T_INT, O(task.callLevel), READONLY, NULL}, + { "read_line", T_INT, O(task.readLine), READONLY, NULL}, + { "motion_line", T_INT, O(task.motionLine), READONLY, NULL}, + { "current_line", T_INT, O(task.currentLine), READONLY, NULL}, + { "file", T_STRING_INPLACE, O(task.file), READONLY, NULL}, + { "command", T_STRING_INPLACE, O(task.command), READONLY, NULL}, + { "program_units", T_INT, O(task.programUnits), READONLY, NULL}, + { "interpreter_errcode", T_INT, O(task.interpreter_errcode), READONLY, NULL}, + { "optional_stop", T_BOOL, O(task.optional_stop_state), READONLY, NULL}, + { "block_delete", T_BOOL, O(task.block_delete_state), READONLY, NULL}, + { "task_paused", T_INT, O(task.task_paused), READONLY, NULL}, + { "input_timeout", T_BOOL, O(task.input_timeout), READONLY, NULL}, + { "rotation_xy", T_DOUBLE, O(task.rotation_xy), READONLY, NULL}, + { "ini_filename", T_STRING_INPLACE, O(task.ini_filename), READONLY, NULL}, + { "delay_left", T_DOUBLE, O(task.delayLeft), READONLY, NULL}, + { "queued_mdi_commands", T_INT, O(task.queuedMDIcommands), READONLY, + "Number of MDI commands queued waiting to run." }, // EMC_TRAJ_STAT traj - {(char*)"linear_units", T_DOUBLE, O(motion.traj.linearUnits), READONLY, NULL}, - {(char*)"angular_units", T_DOUBLE, O(motion.traj.angularUnits), READONLY, NULL}, - {(char*)"cycle_time", T_DOUBLE, O(motion.traj.cycleTime), READONLY, NULL}, - {(char*)"joints", T_INT, O(motion.traj.joints), READONLY, NULL}, - {(char*)"spindles", T_INT, O(motion.traj.spindles), READONLY, NULL}, - {(char*)"axis_mask", T_INT, O(motion.traj.axis_mask), READONLY, NULL}, - {(char*)"motion_mode", T_INT, O(motion.traj.mode), READONLY, (char*)"The current mode of the Motion controller. One of TRAJ_MODE_FREE,\n" + { "linear_units", T_DOUBLE, O(motion.traj.linearUnits), READONLY, NULL}, + { "angular_units", T_DOUBLE, O(motion.traj.angularUnits), READONLY, NULL}, + { "cycle_time", T_DOUBLE, O(motion.traj.cycleTime), READONLY, NULL}, + { "joints", T_INT, O(motion.traj.joints), READONLY, NULL}, + { "spindles", T_INT, O(motion.traj.spindles), READONLY, NULL}, + { "axis_mask", T_INT, O(motion.traj.axis_mask), READONLY, NULL}, + { "motion_mode", T_INT, O(motion.traj.mode), READONLY, + "The current mode of the Motion controller. One of TRAJ_MODE_FREE,\n" "TRAJ_MODE_COORD, or TRAJ_MODE_TELEOP." }, - {(char*)"enabled", T_BOOL, O(motion.traj.enabled), READONLY, NULL}, - {(char*)"inpos", T_BOOL, O(motion.traj.inpos), READONLY, NULL}, - {(char*)"queue", T_INT, O(motion.traj.queue), READONLY, NULL}, - {(char*)"active_queue", T_INT, O(motion.traj.activeQueue), READONLY, NULL}, - {(char*)"queue_full", T_BOOL, O(motion.traj.queueFull), READONLY, NULL}, - {(char*)"motion_id", T_INT, O(motion.traj.id), READONLY, NULL}, - {(char*)"paused", T_BOOL, O(motion.traj.paused), READONLY, NULL}, - {(char*)"single_stepping", T_BOOL, O(motion.traj.single_stepping), READONLY, NULL}, - {(char*)"feedrate", T_DOUBLE, O(motion.traj.scale), READONLY, NULL}, - {(char*)"rapidrate", T_DOUBLE, O(motion.traj.rapid_scale), READONLY, NULL}, - {(char*)"velocity", T_DOUBLE, O(motion.traj.velocity), READONLY, NULL}, - {(char*)"acceleration", T_DOUBLE, O(motion.traj.acceleration), READONLY, NULL}, - {(char*)"max_velocity", T_DOUBLE, O(motion.traj.maxVelocity), READONLY, NULL}, - {(char*)"max_acceleration", T_DOUBLE, O(motion.traj.maxAcceleration), READONLY, NULL}, - {(char*)"probe_tripped", T_BOOL, O(motion.traj.probe_tripped), READONLY, NULL}, - {(char*)"probing", T_BOOL, O(motion.traj.probing), READONLY, NULL}, - {(char*)"probe_val", T_INT, O(motion.traj.probeval), READONLY, NULL}, - {(char*)"kinematics_type", T_INT, O(motion.traj.kinematics_type), READONLY, NULL}, - {(char*)"motion_type", T_INT, O(motion.traj.motion_type), READONLY, (char*)"The type of the currently executing motion (one of MOTION_TYPE_TRAVERSE,\n" + { "enabled", T_BOOL, O(motion.traj.enabled), READONLY, NULL}, + { "inpos", T_BOOL, O(motion.traj.inpos), READONLY, NULL}, + { "queue", T_INT, O(motion.traj.queue), READONLY, NULL}, + { "active_queue", T_INT, O(motion.traj.activeQueue), READONLY, NULL}, + { "queue_full", T_BOOL, O(motion.traj.queueFull), READONLY, NULL}, + { "motion_id", T_INT, O(motion.traj.id), READONLY, NULL}, + { "paused", T_BOOL, O(motion.traj.paused), READONLY, NULL}, + { "single_stepping", T_BOOL, O(motion.traj.single_stepping), READONLY, NULL}, + { "feedrate", T_DOUBLE, O(motion.traj.scale), READONLY, NULL}, + { "rapidrate", T_DOUBLE, O(motion.traj.rapid_scale), READONLY, NULL}, + { "velocity", T_DOUBLE, O(motion.traj.velocity), READONLY, NULL}, + { "acceleration", T_DOUBLE, O(motion.traj.acceleration), READONLY, NULL}, + { "max_velocity", T_DOUBLE, O(motion.traj.maxVelocity), READONLY, NULL}, + { "max_acceleration", T_DOUBLE, O(motion.traj.maxAcceleration), READONLY, NULL}, + { "probe_tripped", T_BOOL, O(motion.traj.probe_tripped), READONLY, NULL}, + { "probing", T_BOOL, O(motion.traj.probing), READONLY, NULL}, + { "probe_val", T_INT, O(motion.traj.probeval), READONLY, NULL}, + { "kinematics_type", T_INT, O(motion.traj.kinematics_type), READONLY, NULL}, + { "motion_type", T_INT, O(motion.traj.motion_type), READONLY, + "The type of the currently executing motion (one of MOTION_TYPE_TRAVERSE,\n" "MOTION_TYPE_FEED, MOTION_TYPE_ARC, MOTION_TYPE_TOOLCHANGE,\n" "MOTION_TYPE_PROBING, or MOTION_TYPE_INDEXROTARY), or 0 if no motion is\n" "currently taking place."}, - {(char*)"distance_to_go", T_DOUBLE, O(motion.traj.distance_to_go), READONLY, NULL}, - {(char*)"current_vel", T_DOUBLE, O(motion.traj.current_vel), READONLY, NULL}, - {(char*)"feed_override_enabled", T_BOOL, O(motion.traj.feed_override_enabled), READONLY, NULL}, - {(char*)"adaptive_feed_enabled", T_BOOL, O(motion.traj.adaptive_feed_enabled), READONLY, NULL}, - {(char*)"feed_hold_enabled", T_BOOL, O(motion.traj.feed_hold_enabled), READONLY, NULL}, - {(char*)"num_extrajoints", T_INT, O(motion.numExtraJoints), READONLY, NULL}, + { "distance_to_go", T_DOUBLE, O(motion.traj.distance_to_go), READONLY, NULL}, + { "current_vel", T_DOUBLE, O(motion.traj.current_vel), READONLY, NULL}, + { "feed_override_enabled", T_BOOL, O(motion.traj.feed_override_enabled), READONLY, NULL}, + { "adaptive_feed_enabled", T_BOOL, O(motion.traj.adaptive_feed_enabled), READONLY, NULL}, + { "feed_hold_enabled", T_BOOL, O(motion.traj.feed_hold_enabled), READONLY, NULL}, + { "num_extrajoints", T_INT, O(motion.numExtraJoints), READONLY, NULL}, // EMC_SPINDLE_STAT motion.spindle @@ -493,30 +970,32 @@ static PyMemberDef Stat_members[] = { // io // EMC_TOOL_STAT io.tool - {(char*)"pocket_prepped", T_INT, O(io.tool.pocketPrepped), READONLY, - (char*)"The index into the stat.tool_table list of the tool currently prepped for\n" + { "pocket_prepped", T_INT, O(io.tool.pocketPrepped), READONLY, + "The index into the stat.tool_table list of the tool currently prepped for\n" "tool change, or -1 no tool is prepped. On a Random toolchanger this is the\n" "same as the tool's pocket number. On a Non-random toolchanger it's a random\n" "small integer." }, - {(char*)"tool_in_spindle", T_INT, O(io.tool.toolInSpindle), READONLY, - (char*)"The tool number of the currently loaded tool, or 0 if no tool is loaded." + { "tool_in_spindle", T_INT, O(io.tool.toolInSpindle), READONLY, + "The tool number of the currently loaded tool, or 0 if no tool is loaded." }, - {(char*)"tool_from_pocket", T_INT, O(io.tool.toolFromPocket), READONLY, - (char*)"The pocket number that the currently loaded tool was retrieved from,\n" + { "tool_from_pocket", T_INT, O(io.tool.toolFromPocket), READONLY, + "The pocket number that the currently loaded tool was retrieved from,\n" "or 0 if no tool is loaded." }, // EMC_COOLANT_STAT io.cooland - {(char*)"mist", T_INT, O(io.coolant.mist), READONLY, NULL}, - {(char*)"flood", T_INT, O(io.coolant.flood), READONLY, NULL}, + { "mist", T_INT, O(io.coolant.mist), READONLY, NULL}, + { "flood", T_INT, O(io.coolant.flood), READONLY, NULL}, // EMC_AUX_STAT io.aux - {(char*)"estop", T_INT, O(io.aux.estop), READONLY, NULL}, + { "estop", T_INT, O(io.aux.estop), READONLY, NULL}, - {(char*)"debug", T_INT, O(debug), READONLY, NULL}, + { "debug", T_INT, O(debug), READONLY, NULL}, {} }; +#undef O +#pragma GCC diagnostic pop static PyObject *int_array(int *arr, int sz) { PyObject *res = PyTuple_New(sz); @@ -947,7 +1426,7 @@ static int Command_init(pyCommandChannel *self, PyObject * /*a*/, PyObject * /*k } static void Command_dealloc(PyObject *self) { - delete ((pyCommandChannel*)self)->c; + delete reinterpret_cast(self)->c; PyObject_Del(self); } @@ -1775,7 +2254,7 @@ static PyObject* Error_poll(pyErrorChannel *s, PyObject *) { } static void Error_dealloc(PyObject *self) { - delete ((pyErrorChannel*)self)->c; + delete reinterpret_cast(self)->c; PyObject_Del(self); } diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index 60626c80324..3eff037ccea 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -187,7 +187,7 @@ def General_Halt(): lathe = 0 mdi_history_max_entries = 1000 mdi_history_save_filename =\ - inifile.find('DISPLAY', 'MDI_HISTORY_FILE') or "~/.axis_mdi_history" + inifile.getstring('DISPLAY', 'MDI_HISTORY_FILE', fallback="~/.axis_mdi_history") feedrate_blackout = 0 @@ -1254,13 +1254,13 @@ def open_file_guts(f, filtered=False, addrecent=True): shutil.copy(parameter, temp_parameter) canon.parameter_file = temp_parameter - timeout = inifile.find("DISPLAY", "PREVIEW_TIMEOUT") or "" + timeout = inifile.getstring("DISPLAY", "PREVIEW_TIMEOUT", fallback="") if timeout: canon.set_timeout(float(timeout)) - initcode = inifile.find("EMC", "RS274NGC_STARTUP_CODE") or "" + initcode = inifile.getstring("EMC", "RS274NGC_STARTUP_CODE", fallback="") if initcode == "": - initcode = inifile.find("RS274NGC", "RS274NGC_STARTUP_CODE") or "" + initcode = inifile.getstring("RS274NGC", "RS274NGC_STARTUP_CODE", fallback="") initcodes = [] if initcode: initcodes.append(initcode) @@ -2247,7 +2247,7 @@ def onoff_clicked(event=None): s.poll() if s.task_state == linuxcnc.STATE_ESTOP_RESET: c.state(linuxcnc.STATE_ON) - homing_prompt = bool(inifile.find("DISPLAY", "HOMING_PROMPT")) + homing_prompt = inifile.getbool("DISPLAY", "HOMING_PROMPT") if homing_prompt: run_homing = prompt_areyousure(_("Homing request"),_("After turning On the machine power,\nYou need find axes origins.\n\n Run homing process?")) if run_homing: @@ -3384,22 +3384,22 @@ def units(s, d=1.0): except ValueError: return unit_values.get(s, d) -random_toolchanger = int(inifile.find("EMCIO", "RANDOM_TOOLCHANGER") or 0) +random_toolchanger = inifile.getbool("EMCIO", "RANDOM_TOOLCHANGER", fallback=False) vars.emcini.set(sys.argv[2]) -jointcount = int(inifile.find("KINS", "JOINTS")) -open_directory = inifile.find("DISPLAY", "PROGRAM_PREFIX") or open_directory -vars.machine.set(inifile.find("EMC", "MACHINE")) +jointcount = inifile.getint("KINS", "JOINTS", fallback=0) +open_directory = inifile.getstring("DISPLAY", "PROGRAM_PREFIX", fallback=open_directory) +vars.machine.set(inifile.getstring("EMC", "MACHINE", fallback="")) extensions = inifile.findall("FILTER", "PROGRAM_EXTENSION") extensions = [e.split(None, 1) for e in extensions] extensions = tuple([(v, tuple(k.split(","))) for k, v in extensions]) postgui_halfile = inifile.findall("HAL", "POSTGUI_HALFILE") or None postgui_halcmds = inifile.findall("HAL", "POSTGUI_HALCMD") or None -max_feed_override = float(inifile.find("DISPLAY", "MAX_FEED_OVERRIDE") or 1.0) -max_spindle_override = float(inifile.find("DISPLAY", "MAX_SPINDLE_OVERRIDE") or max_feed_override) +max_feed_override = inifile.getreal("DISPLAY", "MAX_FEED_OVERRIDE", fallback=1.0) +max_spindle_override = inifile.getreal("DISPLAY", "MAX_SPINDLE_OVERRIDE", fallback=max_feed_override) max_feed_override = int(max_feed_override * 100 + 0.5) max_spindle_override = int(max_spindle_override * 100 + 0.5) -default_spindle_speed = int(inifile.find("DISPLAY", "DEFAULT_SPINDLE_SPEED") or 1) -geometry = inifile.find("DISPLAY", "GEOMETRY") or "XYZABCUVW" +default_spindle_speed = inifile.getint("DISPLAY", "DEFAULT_SPINDLE_SPEED", fallback=1) +geometry = inifile.getstring("DISPLAY", "GEOMETRY", fallback="XYZABCUVW") geometry = re.split(" *(-?[XYZABCUVW])", geometry.upper()) geometry = "".join(reversed(geometry)) @@ -3413,8 +3413,8 @@ def units(s, d=1.0): joint_sequence = [None] * jointcount for j in range(jointcount): section = "JOINT_%d" % j - joint_type[j] = inifile.find(section, "TYPE") or "LINEAR" - joint_sequence[j] = inifile.find(section, "HOME_SEQUENCE") or "" + joint_type[j] = inifile.getstring(section, "TYPE", fallback="LINEAR") + joint_sequence[j] = inifile.getstring(section, "HOME_SEQUENCE", fallback="") axis_type = [None] * linuxcnc.MAX_AXIS for a in range(linuxcnc.MAX_AXIS): @@ -3437,6 +3437,7 @@ def units(s, d=1.0): or ("LINEAR" in axis_type) ) # Search rules for slider items +# FIXME: These ini-values are not type-checked. max_linear_speed = ( inifile.find("DISPLAY","MAX_LINEAR_VELOCITY") or inifile.find("TRAJ","MAX_LINEAR_VELOCITY") @@ -3536,9 +3537,9 @@ def units(s, d=1.0): vars.coord_type.set(inifile.find("DISPLAY", "POSITION_OFFSET") == "RELATIVE") vars.display_type.set(inifile.find("DISPLAY", "POSITION_FEEDBACK") == "COMMANDED") coordinate_display = inifile.find("DISPLAY", "POSITION_UNITS") -lathe = bool(inifile.find("DISPLAY", "LATHE")) -lathe_backtool = bool(inifile.find("DISPLAY", "BACK_TOOL_LATHE")) -foam = bool(inifile.find("DISPLAY", "FOAM")) +lathe = inifile.getbool("DISPLAY", "LATHE", fallback=False) +lathe_backtool = inifile.getbool("DISPLAY", "BACK_TOOL_LATHE", fallback=False) +foam = inifile.getbool("DISPLAY", "FOAM", fallback=False) editor = inifile.find("DISPLAY", "EDITOR") vars.has_editor.set(editor is not None) @@ -3551,7 +3552,7 @@ def units(s, d=1.0): default_tooleditor = "tooledit" if db_program is not None: default_tooleditor = None -tooleditor = inifile.find("DISPLAY","TOOL_EDITOR") or default_tooleditor +tooleditor = inifile.getstring("DISPLAY","TOOL_EDITOR", fallback=default_tooleditor) if inifile.find("RS274NGC", "PARAMETER_FILE") is None: raise SystemExit("Missing INI file setting for [RS274NGC]PARAMETER_FILE") @@ -3559,9 +3560,9 @@ def units(s, d=1.0): lu = units(inifile.find("TRAJ", "LINEAR_UNITS")) except TypeError: raise SystemExit("Missing [TRAJ]LINEAR_UNITS or ANGULAR_UNITS") -a_axis_wrapped = inifile.find("AXIS_A", "WRAPPED_ROTARY") -b_axis_wrapped = inifile.find("AXIS_B", "WRAPPED_ROTARY") -c_axis_wrapped = inifile.find("AXIS_C", "WRAPPED_ROTARY") +a_axis_wrapped = inifile.getbool("AXIS_A", "WRAPPED_ROTARY", fallback=False) +b_axis_wrapped = inifile.getbool("AXIS_B", "WRAPPED_ROTARY", fallback=False) +c_axis_wrapped = inifile.getbool("AXIS_C", "WRAPPED_ROTARY", fallback=False) if coordinate_display: if coordinate_display.lower() in ("mm", "metric"): vars.metric.set(1) else: vars.metric.set(0) @@ -3583,12 +3584,12 @@ def units(s, d=1.0): homing_order_defined = 0 break -ct = float(inifile.find('DISPLAY', 'CYCLE_TIME') or .020) +ct = inifile.getreal('DISPLAY', 'CYCLE_TIME', fallback=.020) if ct < 1: update_ms = int(ct * 1000) else: update_ms = int(ct) -interpname = inifile.find("TASK", "INTERPRETER") or "" +interpname = inifile.getstring("TASK", "INTERPRETER", fallback="") s = linuxcnc.stat(); s.poll() @@ -3627,7 +3628,7 @@ def units(s, d=1.0): widgets.unhomemenu.add_command(command=commands.unhome_all_joints) root_window.tk.call("setup_menu_accel", widgets.unhomemenu, "end", _("Unhome All %s") % ja_name) -kinsmodule=inifile.find("KINS", "KINEMATICS") +kinsmodule=inifile.getstring("KINS", "KINEMATICS", fallback="") kins_is_trivkins = False if kinsmodule.split()[0] == "trivkins": kins_is_trivkins = True @@ -3733,7 +3734,7 @@ def aletter_for_jnum(jnum): a = "XYZABCUVW"[a] if s.axis_mask & (1< 1 and sys.argv[1] == '-ini': ini = linuxcnc.ini(sys.argv[2]) - linuxcnc.nmlfile = ini.find("EMC", "NML_FILE") or linuxcnc.nmlfile + linuxcnc.nmlfile = ini.getstring("EMC", "NML_FILE", fallback=linuxcnc.nmlfile) del sys.argv[1:3] s = linuxcnc.stat(); s.poll() diff --git a/src/emc/usr_intf/emclcd.cc b/src/emc/usr_intf/emclcd.cc index 00c2257478f..51f677086b8 100644 --- a/src/emc/usr_intf/emclcd.cc +++ b/src/emc/usr_intf/emclcd.cc @@ -51,7 +51,6 @@ #include "nml_intf/canon.hh" // CANON_UNITS, CANON_UNITS_INCHES,MM,CM #include "nml_intf/emcglb.h" // EMC_NMLFILE, TRAJ_MAX_VELOCITY, etc. #include "nml_intf/emccfg.h" // DEFAULT_TRAJ_MAX_VELOCITY -#include "libnml/inifile/inifile.hh" // INIFILE #include "config.h" // Standard path definitions #include "libnml/rcs/rcs_print.hh" #include "sockets.h" // TCP/IP common socket functions diff --git a/src/emc/usr_intf/emcrsh.cc b/src/emc/usr_intf/emcrsh.cc index 9a9d9f4f1e3..74893128cf5 100644 --- a/src/emc/usr_intf/emcrsh.cc +++ b/src/emc/usr_intf/emcrsh.cc @@ -40,9 +40,11 @@ #include "shcom.hh" #include "nml_intf/emcglb.h" -#include "libnml/inifile/inifile.hh" +#include #include "libnml/os_intf/timer.hh" +using namespace linuxcnc; + /* * Using linuxcncrsh: see man page linuxcncrsh.1 */ @@ -481,13 +483,11 @@ static int compareNoCase(const std::string &a, const std::string &b) static std::optional getIniVar(const std::string &var, const std::string §ion) { - IniFile inifile; - if (!inifile.Open(emc_inifile)) + IniFile inifile(emc_inifile); + if(!inifile) return nullptr; - auto inistr = inifile.Find(var.c_str(), section.c_str()); - inifile.Close(); - return inistr; + return inifile.findString(var, section); } static inline bool isEnabled(const connectionRecType &ctx) diff --git a/src/emc/usr_intf/emcsched.cc b/src/emc/usr_intf/emcsched.cc index 5b6f49929a4..336c42352fb 100644 --- a/src/emc/usr_intf/emcsched.cc +++ b/src/emc/usr_intf/emcsched.cc @@ -32,7 +32,6 @@ #include "nml_intf/canon.hh" // CANON_UNITS, CANON_UNITS_INCHES,MM,CM #include "nml_intf/emcglb.h" // EMC_NMLFILE, TRAJ_MAX_VELOCITY, etc. #include "nml_intf/emccfg.h" // DEFAULT_TRAJ_MAX_VELOCITY -#include "libnml/inifile/inifile.hh" // INIFILE #include "libnml/nml/nml_oi.hh" // nmlErrorFormat, NML_ERROR, etc #include "libnml/rcs/rcs_print.hh" #include "libnml/os_intf/timer.hh" // esleep diff --git a/src/emc/usr_intf/emcsh.cc b/src/emc/usr_intf/emcsh.cc index 93c141a65a7..160f8e8a05f 100644 --- a/src/emc/usr_intf/emcsh.cc +++ b/src/emc/usr_intf/emcsh.cc @@ -31,12 +31,14 @@ #include "nml_intf/canon.hh" // CANON_UNITS, CANON_UNITS_INCHES,MM,CM #include "nml_intf/emcglb.h" // EMC_NMLFILE, TRAJ_MAX_VELOCITY, etc. #include "nml_intf/emccfg.h" // DEFAULT_TRAJ_MAX_VELOCITY -#include "libnml/inifile/inifile.hh" // INIFILE +#include #include "libnml/rcs/rcs_print.hh" #include "libnml/os_intf/timer.hh" #include "shcom.hh" +using namespace linuxcnc; + #define setresult(t,s) Tcl_SetObjResult((t), Tcl_NewStringObj((s),-1)) #ifndef CONST @@ -388,7 +390,7 @@ static int emc_plat(ClientData /*clientdata*/, static int emc_ini(ClientData /*clientdata*/, Tcl_Interp * interp, int objc, Tcl_Obj * CONST objv[]) { - IniFile inifile; + IniFile inifile(emc_inifile); const char *varstr, *secstr, *defaultstr; defaultstr = 0; @@ -396,8 +398,8 @@ static int emc_ini(ClientData /*clientdata*/, setresult(interp,"emc_ini: need 'var' and 'section'"); return TCL_ERROR; } - // open it - if (inifile.Open(emc_inifile) == false) { + if (!inifile) { + setresult(interp, "emc_ini: failed to open ini-file'"); return TCL_OK; } @@ -408,7 +410,7 @@ static int emc_ini(ClientData /*clientdata*/, defaultstr = Tcl_GetStringFromObj(objv[3], 0); } - auto inistring = inifile.Find(varstr, secstr); + auto inistring = inifile.findString(varstr, secstr); if (!inistring) { if (defaultstr != 0) { setresult(interp,(char *) defaultstr); @@ -418,9 +420,6 @@ static int emc_ini(ClientData /*clientdata*/, setresult(interp, inistring->c_str()); - // close it - inifile.Close(); - return TCL_OK; } diff --git a/src/emc/usr_intf/gmoccapy/getiniinfo.py b/src/emc/usr_intf/gmoccapy/getiniinfo.py index 63077dc6a50..20be167976e 100644 --- a/src/emc/usr_intf/gmoccapy/getiniinfo.py +++ b/src/emc/usr_intf/gmoccapy/getiniinfo.py @@ -47,9 +47,9 @@ def __init__(self): sys.exit() def get_cycle_time(self): - temp = self.inifile.find("DISPLAY", "CYCLE_TIME") + temp = self.inifile.getint("DISPLAY", "CYCLE_TIME") try: - return int(temp) + return temp except: message = ("Wrong entry [DISPLAY] CYCLE_TIME in INI File! ") message += ("Will use gmoccapy default 150") @@ -69,7 +69,7 @@ def get_preference_file_path(self): # we use gmoccapy.pref in the config dir temp = self.inifile.find("DISPLAY", "PREFERENCE_FILE_PATH") if not temp: - machinename = self.inifile.find("EMC", "MACHINE") + machinename = self.inifile.getstring("EMC", "MACHINE", fallback="") if not machinename: temp = os.path.join(CONFIGPATH, "gmoccapy.pref") else: @@ -79,7 +79,7 @@ def get_preference_file_path(self): return temp def get_coordinates(self): - temp = self.inifile.find("TRAJ", "COORDINATES") + temp = self.inifile.getstring("TRAJ", "COORDINATES", fallback="") # get rid of the spaces, if there are some temp = temp.replace(' ','') @@ -89,11 +89,11 @@ def get_coordinates(self): return temp.lower() def get_joints(self): - temp = self.inifile.find("KINS", "JOINTS") + temp = self.inifile.getint("KINS", "JOINTS") if not temp: LOG.warning("No JOINTS entry found in [KINS] of INI file, will use 3 as default") return (3) - return int(temp) + return temp def get_axis_list(self): axis_list = [] @@ -115,6 +115,7 @@ def get_axis_list(self): def get_joint_axis_relation(self): # we will find out the relation between joint and axis. + # FIXME: This will crash if [KINS]KINEMATICS is not in the INI-file temp = self.inifile.find("KINS", "KINEMATICS").split() # follow the order given in $ man trivkins @@ -177,6 +178,7 @@ def get_joint_axis_relation(self): return joint_axis_dic, double_axis_letter def get_trivial_kinematics(self): + # FIXME: This will crash if [KINS]KINEMATICS is not in the INI-file temp = self.inifile.find("KINS", "KINEMATICS").split() LOG.debug("[KINS] KINESTYPE is {0}".format(temp[0])) @@ -199,10 +201,7 @@ def get_trivial_kinematics(self): return False def get_no_force_homing(self): - temp = self.inifile.find("TRAJ", "NO_FORCE_HOMING") - if not temp or temp == "0": - return False - return True + return self.inifile.getbool("TRAJ", "NO_FORCE_HOMING", fallback=False) def get_position_feedback_actual(self): temp = self.inifile.find("DISPLAY", "POSITION_FEEDBACK") @@ -214,99 +213,90 @@ def get_position_feedback_actual(self): return False def get_lathe(self): - temp = self.inifile.find("DISPLAY", "LATHE") - if not temp or temp == "0": - return False - return True + return self.inifile.getbool("DISPLAY", "LATHE", fallback=False) def get_backtool_lathe(self): - temp = self.inifile.find("DISPLAY", "BACK_TOOL_LATHE") - if not temp or temp == "0": - return False - return True + return self.inifile.getbool("DISPLAY", "BACK_TOOL_LATHE", fallback=False) def get_lathe_wear_offsets(self): - temp = self.inifile.find("DISPLAY", "LATHE_WEAR_OFFSETS") - if not temp or temp == "0": - return False - return True + return self.inifile.getbool("DISPLAY", "LATHE_WEAR_OFFSETS", fallback=False) def get_jog_vel(self): # get default jog velocity # must convert from INI's units per second to gmoccapy's units per minute - temp = self.inifile.find("TRAJ", "DEFAULT_LINEAR_VELOCITY") + temp = self.inifile.getreal("TRAJ", "DEFAULT_LINEAR_VELOCITY") if not temp: - temp = self.inifile.find("TRAJ", "MAX_LINEAR_VELOCITY" ) + temp = self.inifile.getreal("TRAJ", "MAX_LINEAR_VELOCITY" ) if temp: - temp = float(temp) / 2 + temp = temp / 2 LOG.warning("No DEFAULT_LINEAR_VELOCITY entry found in [TRAJ] of INI file. Using half on MAX_LINEAR_VELOCITY.") else: temp = 3.0 LOG.warning("No DEFAULT_LINEAR_VELOCITY entry found in [TRAJ] of INI file. Using default value of 180 units / min.") - return float(temp) * 60 + return temp * 60 def get_max_jog_vel(self): # get max jog velocity # must convert from INI's units per second to gmoccapy's units per minute - temp = self.inifile.find("TRAJ", "MAX_LINEAR_VELOCITY") + temp = self.inifile.getreal("TRAJ", "MAX_LINEAR_VELOCITY") if not temp: temp = 10.0 LOG.warning("No MAX_LINEAR_VELOCITY entry found in [TRAJ] of INI file. Using default value of 600 units / min.") - return float(temp) * 60 + return temp * 60 def get_default_ang_jog_vel(self): # get default angular jog velocity - temp = self.inifile.find("DISPLAY", "DEFAULT_ANGULAR_VELOCITY") + temp = self.inifile.getreal("DISPLAY", "DEFAULT_ANGULAR_VELOCITY") if not temp: temp = 360.0 LOG.warning("No DEFAULT_ANGULAR_VELOCITY entry found in [DISPLAY] of INI file. Using default value of 360 degree / min.") - return float(temp) + return temp def get_max_ang_jog_vel(self): # get max angular velocity - temp = self.inifile.find("DISPLAY", "MAX_ANGULAR_VELOCITY") + temp = self.inifile.getreal("DISPLAY", "MAX_ANGULAR_VELOCITY") if not temp: temp = 3600.0 LOG.warning("No MAX_ANGULAR_VELOCITY entry found in [DISPLAY] of INI file. Using default value of 3600 degree / min.") - return float(temp) + return temp def get_min_ang_jog_vel(self): # get min angular velocity - temp = self.inifile.find("DISPLAY", "MIN_ANGULAR_VELOCITY") + temp = self.inifile.getreal("DISPLAY", "MIN_ANGULAR_VELOCITY") if not temp: temp = 0.1 LOG.warning("No MIN_ANGULAR_VELOCITY entry found in [DISPLAY] of INI file. Using default value of 0.1 degree / min.") - return float(temp) + return temp def get_default_spindle_speed(self): # check for default spindle speed settings - temp = self.inifile.find("DISPLAY", "DEFAULT_SPINDLE_SPEED") + temp = self.inifile.getreal("DISPLAY", "DEFAULT_SPINDLE_SPEED") if not temp: - temp = 300 + temp = 300.0 LOG.warning("No DEFAULT_SPINDLE_SPEED entry found in [DISPLAY] of INI file") - return float(temp) + return temp def get_max_spindle_override(self): # check for override settings - temp = self.inifile.find("DISPLAY", "MAX_SPINDLE_OVERRIDE") + temp = self.inifile.getreal("DISPLAY", "MAX_SPINDLE_OVERRIDE") if not temp: temp = 1.0 LOG.warning("No MAX_SPINDLE_OVERRIDE entry found in [DISPLAY] of INI file") - return float(temp) + return temp def get_min_spindle_override(self): - temp = self.inifile.find("DISPLAY", "MIN_SPINDLE_OVERRIDE") + temp = self.inifile.getreal("DISPLAY", "MIN_SPINDLE_OVERRIDE") if not temp: temp = 0.1 LOG.warning("No MIN_SPINDLE_OVERRIDE entry found in [DISPLAY] of INI file") - return float(temp) + return temp def get_max_feed_override(self): - temp = self.inifile.find("DISPLAY", "MAX_FEED_OVERRIDE") + temp = self.inifile.getreal("DISPLAY", "MAX_FEED_OVERRIDE") if not temp: temp = 1.0 LOG.warning("No MAX_FEED_OVERRIDE entry found in [DISPLAY] of INI file") - return float(temp) + return temp def get_embedded_tabs(self): # Check INI file for embed commands @@ -331,10 +321,7 @@ def get_embedded_tabs(self): return tab_names, tab_location, tab_cmd def get_parameter_file(self): - temp = self.inifile.find("RS274NGC", "PARAMETER_FILE") - if not temp: - return False - return temp + return self.inifile.getstring("RS274NGC", "PARAMETER_FILE", fallback=False) def get_program_prefix(self): # and we want to set the default path @@ -379,16 +366,13 @@ def get_increments(self): return jog_increments def get_toolfile(self): - temp = self.inifile.find("EMCIO", "TOOL_TABLE") - if not temp: - return False - return temp + return self.inifile.getstring("EMCIO", "TOOL_TABLE", fallback=False) def get_tool_sensor_data(self): - xpos = self.inifile.find("TOOLSENSOR", "X") - ypos = self.inifile.find("TOOLSENSOR", "Y") - zpos = self.inifile.find("TOOLSENSOR", "Z") - maxprobe = self.inifile.find("TOOLSENSOR", "MAXPROBE") + xpos = self.inifile.getreal("TOOLSENSOR", "X") + ypos = self.inifile.getreal("TOOLSENSOR", "Y") + zpos = self.inifile.getreal("TOOLSENSOR", "Z") + maxprobe = self.inifile.getreal("TOOLSENSOR", "MAXPROBE") return xpos, ypos, zpos, maxprobe def get_macros(self): @@ -432,16 +416,10 @@ def get_subroutine_paths(self): def get_axis_2_min_limit(self): # needed to calculate the offset for automated tool measurement - temp = self.inifile.find("AXIS_2", "MIN_LIMIT") - if not temp: - return False - return float(temp) + return self.inifile.getreal("AXIS_2", "MIN_LIMIT", fallback=False) def get_RS274_start_code(self): - temp = self.inifile.find("RS274NGC", "RS274NGC_STARTUP_CODE") - if not temp: - temp = "" - return temp + return self.inifile.find("RS274NGC", "RS274NGC_STARTUP_CODE", fallback="") def get_user_messages(self): message_text = self.inifile.findall("DISPLAY", "MESSAGE_TEXT") diff --git a/src/emc/usr_intf/gremlin/gremlin.py b/src/emc/usr_intf/gremlin/gremlin.py index fbb1b3de1c5..3a60f9fbc4b 100755 --- a/src/emc/usr_intf/gremlin/gremlin.py +++ b/src/emc/usr_intf/gremlin/gremlin.py @@ -194,22 +194,21 @@ def C(s): self.show_tool = True self.show_dtg = True self.grid_size = 0.0 - temp = inifile.find("DISPLAY", "LATHE") - self.lathe_option = bool(temp == "1" or temp == "True" or temp == "true" ) - self.foam_option = bool(inifile.find("DISPLAY", "FOAM")) + self.lathe_option = self.inifile.getbool("DISPLAY", "LATHE", fallback=False) + self.foam_option = self.inifile.getbool("DISPLAY", "FOAM", fallback=False) self.show_offsets = False self.use_default_controls = True self.mouse_btn_mode = 0 - self.a_axis_wrapped = inifile.find("AXIS_A", "WRAPPED_ROTARY") - self.b_axis_wrapped = inifile.find("AXIS_B", "WRAPPED_ROTARY") - self.c_axis_wrapped = inifile.find("AXIS_C", "WRAPPED_ROTARY") + self.a_axis_wrapped = self.inifile.getbool("AXIS_A", "WRAPPED_ROTARY", fallback=False) + self.b_axis_wrapped = self.inifile.getbool("AXIS_B", "WRAPPED_ROTARY", fallback=False) + self.c_axis_wrapped = self.inifile.getbool("AXIS_C", "WRAPPED_ROTARY", fallback=False) live_axis_count = 0 for i,j in enumerate("XYZABCUVW"): if self.stat.axis_mask & (1< #include "libnml/rcs/rcs_print.hh" #include "libnml/nml/nml_oi.hh" #include "libnml/os_intf/timer.hh" #include #include "tooldata/tooldata.hh" +#include "mapini.hh" +#include "unitenum.hh" + +using namespace linuxcnc; /* Using halui: see the man page */ @@ -468,21 +472,8 @@ static void thisQuit() exit(0); } -static enum { - LINEAR_UNITS_CUSTOM = 1, - LINEAR_UNITS_AUTO, - LINEAR_UNITS_MM, - LINEAR_UNITS_INCH, - LINEAR_UNITS_CM -} linearUnitConversion = LINEAR_UNITS_AUTO; - -static enum { - ANGULAR_UNITS_CUSTOM = 1, - ANGULAR_UNITS_AUTO, - ANGULAR_UNITS_DEG, - ANGULAR_UNITS_RAD, - ANGULAR_UNITS_GRAD -} angularUnitConversion = ANGULAR_UNITS_AUTO; +static LINEAR_UNIT_CONVERSION linearUnitConversion = LINEAR_UNITS_AUTO; +static ANGULAR_UNIT_CONVERSION angularUnitConversion = ANGULAR_UNITS_AUTO; #define CLOSE(a,b,eps) ((a)-(b) < +(eps) && (a)-(b) > -(eps)) #define LINEAR_CLOSENESS 0.0001 @@ -1380,45 +1371,20 @@ static int sendSpindleOverride(int spindle, double override) static int iniLoad(const char *filename) { - IniFile inifile; - char version[LINELEN], machine[LINELEN]; - double d; - int i; + IniFile inifile(filename); - // open it - if (inifile.Open(filename) == false) { + if (!inifile) { return -1; } // EMC debugging flags - emc_debug = 0; // disabled by default - if (auto inistring = inifile.Find("DEBUG", "EMC")) { - // parse to global - if (sscanf(inistring->c_str(), "%x", &emc_debug) < 1) { - perror("failed to parse [EMC] DEBUG"); - } - } + emc_debug = (unsigned)inifile.findUIntV("DEBUG", "EMC", 0); // set output for RCS messages - set_rcs_print_destination(RCS_PRINT_TO_STDOUT); // use stdout by default - if (auto inistring = inifile.Find("RCS_DEBUG_DEST", "EMC")) { - static RCS_PRINT_DESTINATION_TYPE type; - if (*inistring == "STDOUT") { - type = RCS_PRINT_TO_STDOUT; - } else if (*inistring == "STDERR") { - type = RCS_PRINT_TO_STDERR; - } else if (*inistring == "FILE") { - type = RCS_PRINT_TO_FILE; - } else if (*inistring == "LOGGER") { - type = RCS_PRINT_TO_LOGGER; - } else if (*inistring == "MSGBOX") { - type = RCS_PRINT_TO_MESSAGE_BOX; - } else if (*inistring == "NULL") { - type = RCS_PRINT_TO_NULL; - } else { - type = RCS_PRINT_TO_STDOUT; - } - set_rcs_print_destination(type); + if (auto inival = mapRcsDestination(inifile, "RCS_DEBUG_DEST", "EMC")) { + set_rcs_print_destination(*inival); + } else { + set_rcs_print_destination(RCS_PRINT_TO_STDOUT); } // NML/RCS debugging flags @@ -1430,140 +1396,99 @@ static int iniLoad(const char *filename) } // set flags if RCS_DEBUG in ini file - if (auto inistring = inifile.Find("RCS_DEBUG", "EMC")) { - long unsigned int flags; - if (sscanf(inistring->c_str(), "%lx", &flags) < 1) { - perror("failed to parse [EMC] RCS_DEBUG"); - } + if (auto inival = inifile.findUInt("RCS_DEBUG", "EMC")) { // clear all flags clear_rcs_print_flag(PRINT_EVERYTHING); // set parsed flags - set_rcs_print_flag((long)flags); + set_rcs_print_flag((long)*inival); } // output infinite RCS errors by default - max_rcs_errors_to_print = -1; - if (auto inistring = inifile.Find("RCS_MAX_ERR", "EMC")) { - if (sscanf(inistring->c_str(), "%d", &max_rcs_errors_to_print) < 1) { - perror("failed to parse [EMC] RCS_MAX_ERR"); - } - } - - strncpy(version, "unknown", LINELEN-1); - if (auto inistring = inifile.Find("VERSION", "EMC")) { - strncpy(version, inistring->c_str(), LINELEN-1); - } + max_rcs_errors_to_print = inifile.findIntV("RCS_MAX_ERR", "EMC", -1); if (emc_debug & EMC_DEBUG_CONFIG) { - if (auto inistring = inifile.Find("MACHINE", "EMC")) { - strncpy(machine, inistring->c_str(), LINELEN-1); - } else { - strncpy(machine, "unknown", LINELEN-1); - } - + std::string version = inifile.findStringV("VERSION", "EMC", ""); + std::string machine = inifile.findStringV("MACHINE", "EMC", ""); extern char *program_invocation_short_name; rcs_print( "%s (%d) halui: machine '%s' version '%s'\n", - program_invocation_short_name, getpid(), machine, version + program_invocation_short_name, getpid(), machine.c_str(), version.c_str() ); } - if (auto inistring = inifile.Find("NML_FILE", "EMC")) { + if (auto inistring = inifile.findString("NML_FILE", "EMC")) { // copy to global rtapi_strxcpy(emc_nmlfile, inistring->c_str()); - } else { - // not found, use default - } + } // else not found, use default - if (auto inistring = inifile.Find("MAX_FEED_OVERRIDE", "DISPLAY")) { - if (1 == sscanf(inistring->c_str(), "%lf", &d) && d > 0.0) { - maxFeedOverride = d; - } + if (auto inival = inifile.findReal("MAX_FEED_OVERRIDE", "DISPLAY")) { + if (*inival > 0.0) { + maxFeedOverride = *inival; + } } - if(inifile.Find(&maxMaxVelocity, "MAX_LINEAR_VELOCITY", "TRAJ") && - inifile.Find(&maxMaxVelocity, "MAX_VELOCITY", "AXIS_X")) + if(!inifile.isSet("MAX_LINEAR_VELOCITY", "TRAJ") && !inifile.isSet("MAX_VELOCITY", "AXIS_X")) maxMaxVelocity = 1.0; - if (auto inistring = inifile.Find("MIN_SPINDLE_OVERRIDE", "DISPLAY")) { - if (1 == sscanf(inistring->c_str(), "%lf", &d) && d > 0.0) { - minSpindleOverride = d; - } + if (auto inival = inifile.findReal("MIN_SPINDLE_OVERRIDE", "DISPLAY")) { + if (*inival > 0.0) { + minSpindleOverride = *inival; + } } - if (auto inistring = inifile.Find("MAX_SPINDLE_OVERRIDE", "DISPLAY")) { - if (1 == sscanf(inistring->c_str(), "%lf", &d) && d > 0.0) { - maxSpindleOverride = d; - } + if (auto inival = inifile.findReal("MAX_SPINDLE_OVERRIDE", "DISPLAY")) { + if (*inival > 0.0) { + maxSpindleOverride = *inival; + } } - auto coord = inifile.Find("COORDINATES", "TRAJ"); num_axes = 0; - if (coord) { - if(coord->find_first_of("xX") != std::string::npos) { axis_mask |= 0x0001; num_axes++; } - if(coord->find_first_of("yY") != std::string::npos) { axis_mask |= 0x0002; num_axes++; } - if(coord->find_first_of("zZ") != std::string::npos) { axis_mask |= 0x0004; num_axes++; } - if(coord->find_first_of("aA") != std::string::npos) { axis_mask |= 0x0008; num_axes++; } - if(coord->find_first_of("bB") != std::string::npos) { axis_mask |= 0x0010; num_axes++; } - if(coord->find_first_of("cC") != std::string::npos) { axis_mask |= 0x0020; num_axes++; } - if(coord->find_first_of("uU") != std::string::npos) { axis_mask |= 0x0040; num_axes++; } - if(coord->find_first_of("vV") != std::string::npos) { axis_mask |= 0x0080; num_axes++; } - if(coord->find_first_of("wW") != std::string::npos) { axis_mask |= 0x0100; num_axes++; } + axis_mask = 0; + if (auto coord = inifile.findString("COORDINATES", "TRAJ")) { + static std::string axes{"XYZABCUVW"}; + for (auto c : *coord) { + size_t pos = axes.find(std::toupper(c & 0xff)); + if (std::string::npos != pos) { + num_axes++; + axis_mask |= 1 << pos; + } + // else we could warn... + } } if (num_axes ==0) { - rcs_print("halui: no [TRAJ]COORDINATES specified, enabling all axes\n"); - num_axes = EMCMOT_MAX_AXIS; - axis_mask = 0xFFFF; + rcs_print("halui: no [TRAJ]COORDINATES specified, enabling all axes\n"); + num_axes = EMCMOT_MAX_AXIS; + axis_mask = (1 << EMCMOT_MAX_AXIS) - 1; } - if (auto inistring = inifile.Find("JOINTS", "KINS")) { - if (1 == sscanf(inistring->c_str(), "%d", &i) && i > 0) { - num_joints = i; + if (auto inival = inifile.findSInt("JOINTS", "KINS")) { + if (*inival > 0) { + num_joints = *inival; } } - if (auto inistring = inifile.Find("SPINDLES", "TRAJ")) { - if (1 == sscanf(inistring->c_str(), "%d", &i) && i > 0) { - num_spindles = i; + if (auto inival = inifile.findSInt("SPINDLES", "TRAJ")) { + if (*inival > 0) { + num_spindles = *inival; } } - if (inifile.Find("HOME_SEQUENCE", "JOINT_0")) { + if (!inifile.isSet("HOME_SEQUENCE", "JOINT_0")) { have_home_all = 1; } - if (auto inistring = inifile.Find("LINEAR_UNITS", "DISPLAY")) { - if (*inistring == "AUTO") { - linearUnitConversion = LINEAR_UNITS_AUTO; - } else if (*inistring == "INCH") { - linearUnitConversion = LINEAR_UNITS_INCH; - } else if (*inistring == "MM") { - linearUnitConversion = LINEAR_UNITS_MM; - } else if (*inistring == "CM") { - linearUnitConversion = LINEAR_UNITS_CM; - } + if (auto v = mapLinearUnits(inifile, "LINEAR_UNITS", "DISPLAY")) { + linearUnitConversion = *v; } - - if (auto inistring = inifile.Find("ANGULAR_UNITS", "DISPLAY")) { - if (*inistring == "AUTO") { - angularUnitConversion = ANGULAR_UNITS_AUTO; - } else if (*inistring == "DEG") { - angularUnitConversion = ANGULAR_UNITS_DEG; - } else if (*inistring == "RAD") { - angularUnitConversion = ANGULAR_UNITS_RAD; - } else if (*inistring == "GRAD") { - angularUnitConversion = ANGULAR_UNITS_GRAD; - } + if (auto v = mapAngularUnits(inifile, "ANGULAR_UNITS", "DISPLAY")) { + angularUnitConversion = *v; } while(num_mdi_commands < MDI_MAX) { - auto mc = inifile.Find("MDI_COMMAND", "HALUI", num_mdi_commands+1); + auto mc = inifile.findString(num_mdi_commands+1, "MDI_COMMAND", "HALUI"); if (!mc) break; mdi_commands[num_mdi_commands++] = strdup(mc->c_str()); } - // close it - inifile.Close(); - return 0; } diff --git a/src/emc/usr_intf/mapini.cc b/src/emc/usr_intf/mapini.cc new file mode 100644 index 00000000000..fa9cd0076b5 --- /dev/null +++ b/src/emc/usr_intf/mapini.cc @@ -0,0 +1,67 @@ +// +// Ini-value mapping for ini-file entries +// Copyright (C) 2026 B.Stultiens +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#include "mapini.hh" + +#include "libnml/rcs/rcs_print.hh" +#include "unitenum.hh" + +using namespace linuxcnc; + +std::optional mapRcsDestination(const IniFile &ini, const std::string &var, const std::string &sec) +{ + static const std::map rcsDestinationMap = { + { "STDOUT", RCS_PRINT_TO_STDOUT }, + { "STDERR", RCS_PRINT_TO_STDERR }, + { "FILE", RCS_PRINT_TO_FILE }, + { "LOGGER", RCS_PRINT_TO_LOGGER }, + { "MSGBOX", RCS_PRINT_TO_MESSAGE_BOX }, + { "NULL", RCS_PRINT_TO_NULL }, + }; + if (auto val = ini.findMap(rcsDestinationMap, var, sec)) + return *val; + return std::nullopt; +} + +std::optional mapLinearUnits(const IniFile &ini, const std::string &var, const std::string &sec) +{ + static const std::map linUnitMap = { + { "AUTO", LINEAR_UNITS_AUTO }, + { "INCH", LINEAR_UNITS_INCH }, + { "MM", LINEAR_UNITS_MM }, + { "CM", LINEAR_UNITS_CM }, + }; + if (auto inival = ini.findMap(linUnitMap, var, sec)) + return *inival; + return std::nullopt; +} + +std::optional mapAngularUnits(const IniFile &ini, const std::string &var, const std::string &sec) +{ + static const std::map angUnitMap = { + { "AUTO", ANGULAR_UNITS_AUTO }, + { "DEG", ANGULAR_UNITS_DEG }, + { "RAD", ANGULAR_UNITS_RAD }, + { "GRAD", ANGULAR_UNITS_GRAD }, + }; + if (auto inival = ini.findMap(angUnitMap, var, sec)) + return *inival; + return std::nullopt; +} + +// vim: ts=4 sw=4 diff --git a/src/emc/usr_intf/mapini.hh b/src/emc/usr_intf/mapini.hh new file mode 100644 index 00000000000..340013a2dc3 --- /dev/null +++ b/src/emc/usr_intf/mapini.hh @@ -0,0 +1,36 @@ +// +// Ini-value mapping for ini-file entries +// Copyright (C) 2026 B.Stultiens +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#ifndef __LINUXCNC_USR_INTF_MAPINI_HH +#define __LINUXCNC_USR_INTF_MAPINI_HH + +#include + +// Forward declarations +enum RCS_PRINT_DESTINATION_TYPE : int; +enum LINEAR_UNIT_CONVERSION : int; +enum ANGULAR_UNIT_CONVERSION : int; + +std::optional mapRcsDestination(const linuxcnc::IniFile &ini, + const std::string &var, const std::string &sec); +std::optional mapLinearUnits(const linuxcnc::IniFile &ini, + const std::string &var, const std::string &sec); +std::optional mapAngularUnits(const linuxcnc::IniFile &ini, + const std::string &var, const std::string &sec); + +#endif diff --git a/src/emc/usr_intf/pyui/commands.py b/src/emc/usr_intf/pyui/commands.py index dd8f4fad10a..b80fde587bc 100644 --- a/src/emc/usr_intf/pyui/commands.py +++ b/src/emc/usr_intf/pyui/commands.py @@ -16,7 +16,7 @@ inifile = linuxcnc.ini(os.environ['INI_FILE_NAME']) trajcoordinates = inifile.find("TRAJ", "COORDINATES").lower().replace(" ","") -jointcount = int(inifile.find("KINS","JOINTS")) +jointcount = inifile.getint("KINS","JOINTS") DBG_state = 0 def DBG(str): diff --git a/src/emc/usr_intf/qtplasmac/qtplasmac_gcode.py b/src/emc/usr_intf/qtplasmac/qtplasmac_gcode.py index 481d39c4382..aeff3b6103f 100755 --- a/src/emc/usr_intf/qtplasmac/qtplasmac_gcode.py +++ b/src/emc/usr_intf/qtplasmac/qtplasmac_gcode.py @@ -52,7 +52,7 @@ def __init__(self, *args): print(line.strip()) sys.exit() self.set_gui_type() - self.machine = INI.find('EMC', 'MACHINE') + self.machine = INI.getstring('EMC', 'MACHINE', fallback="") self.filteredBkp = f'{self.tmpPath}/filtered_bkp.ngc' self.errorFile = f'{self.tmpPath}/gcode_errors.txt' self.materialFile = f'{self.machine}_material.cfg' diff --git a/src/emc/usr_intf/schedrmt.cc b/src/emc/usr_intf/schedrmt.cc index 6a5cccd2835..3710f35d85f 100644 --- a/src/emc/usr_intf/schedrmt.cc +++ b/src/emc/usr_intf/schedrmt.cc @@ -38,7 +38,6 @@ #include "nml_intf/canon.hh" // CANON_UNITS, CANON_UNITS_INCHES,MM,CM #include "nml_intf/emcglb.h" // EMC_NMLFILE, TRAJ_MAX_VELOCITY, etc. #include "nml_intf/emccfg.h" // DEFAULT_TRAJ_MAX_VELOCITY -#include "libnml/inifile/inifile.hh" // INIFILE #include "libnml/rcs/rcs_print.hh" #include "libnml/os_intf/timer.hh" // etime() #include "shcom.hh" // NML Messaging functions diff --git a/src/emc/usr_intf/shcom.cc b/src/emc/usr_intf/shcom.cc index 4b6ae571e92..d14bb10addc 100644 --- a/src/emc/usr_intf/shcom.cc +++ b/src/emc/usr_intf/shcom.cc @@ -14,32 +14,25 @@ * Last change: ********************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include #include // PM_POSE, TO_RAD +#include #include "libnml/rcs/rcs.hh" #include "nml_intf/emc.hh" // EMC NML #include "nml_intf/emc_nml.hh" #include "nml_intf/canon.hh" // CANON_UNITS, CANON_UNITS_INCHES,MM,CM #include "nml_intf/emcglb.h" // EMC_NMLFILE, TRAJ_MAX_VELOCITY, etc. #include "nml_intf/emccfg.h" // DEFAULT_TRAJ_MAX_VELOCITY -#include "libnml/inifile/inifile.hh" // INIFILE #include "libnml/nml/nml_oi.hh" // nmlErrorFormat, NML_ERROR, etc #include "libnml/rcs/rcs_print.hh" #include "libnml/os_intf/timer.hh" // esleep +#include "mapini.hh" #include "shcom.hh" // Common NML communications functions +using namespace linuxcnc; + LINEAR_UNIT_CONVERSION linearUnitConversion = LINEAR_UNITS_AUTO; ANGULAR_UNIT_CONVERSION angularUnitConversion = ANGULAR_UNITS_AUTO; @@ -964,45 +957,20 @@ int sendProbe(double x, double y, double z) int iniLoad(const char *filename) { - IniFile inifile; - char displayString[LINELEN] = ""; - int t; - int i; + IniFile inifile(filename); - // open it - if (inifile.Open(filename) == false) { + if (!inifile) { return -1; } // EMC debugging flags - emc_debug = 0; // disabled by default - if (auto inistring = inifile.Find("DEBUG", "EMC")) { - // parse to global - if (sscanf(inistring->c_str(), "%x", &emc_debug) < 1) { - perror("failed to parse [EMC] DEBUG"); - } - } + emc_debug = (unsigned)inifile.findUIntV("DEBUG", "EMC", 0); // set output for RCS messages - set_rcs_print_destination(RCS_PRINT_TO_STDOUT); // use stdout by default - if (auto inistring = inifile.Find("RCS_DEBUG_DEST", "EMC")) { - static RCS_PRINT_DESTINATION_TYPE type; - if (*inistring == "STDOUT") { - type = RCS_PRINT_TO_STDOUT; - } else if (*inistring == "STDERR") { - type = RCS_PRINT_TO_STDERR; - } else if (*inistring == "FILE") { - type = RCS_PRINT_TO_FILE; - } else if (*inistring == "LOGGER") { - type = RCS_PRINT_TO_LOGGER; - } else if (*inistring == "MSGBOX") { - type = RCS_PRINT_TO_MESSAGE_BOX; - } else if (*inistring == "NULL") { - type = RCS_PRINT_TO_NULL; - } else { - type = RCS_PRINT_TO_STDOUT; - } - set_rcs_print_destination(type); + if (auto inival = mapRcsDestination(inifile, "RCS_DEBUG_DEST", "EMC")) { + set_rcs_print_destination(*inival); + } else { + set_rcs_print_destination(RCS_PRINT_TO_STDOUT); } // NML/RCS debugging flags @@ -1014,35 +982,21 @@ int iniLoad(const char *filename) } // set flags if RCS_DEBUG in ini file - if (auto inistring = inifile.Find("RCS_DEBUG", "EMC")) { - long unsigned int flags; - if (sscanf(inistring->c_str(), "%lx", &flags) < 1) { - perror("failed to parse [EMC] RCS_DEBUG"); - } + if (auto dbg = inifile.findUInt("RCS_DEBUG", "EMC")) { // clear all flags clear_rcs_print_flag(PRINT_EVERYTHING); // set parsed flags - set_rcs_print_flag((long)flags); + set_rcs_print_flag((long)*dbg); } // output infinite RCS errors by default max_rcs_errors_to_print = -1; - if (auto inistring = inifile.Find("RCS_MAX_ERR", "EMC")) { - if (sscanf(inistring->c_str(), "%d", &max_rcs_errors_to_print) < 1) { - perror("failed to parse [EMC] RCS_MAX_ERR"); - } + if (auto inival = inifile.findSInt("RCS_MAX_ERR", "EMC")) { + max_rcs_errors_to_print = *inival; } if (emc_debug & EMC_DEBUG_CONFIG) { - std::string version = ""; - std::string machine = ""; - if (auto inistring = inifile.Find("VERSION", "EMC")) { - version = *inistring; - } - - if (auto inistring = inifile.Find("MACHINE", "EMC")) { - machine = *inistring; - } - + std::string version = inifile.findStringV("VERSION", "EMC", ""); + std::string machine = inifile.findStringV("MACHINE", "EMC", ""); extern char *program_invocation_short_name; rcs_print( "%s (%d) shcom: machine '%s' version '%s'\n", @@ -1050,53 +1004,29 @@ int iniLoad(const char *filename) ); } - if (auto inistring = inifile.Find("NML_FILE", "EMC")) { + if (auto inistring = inifile.findString("NML_FILE", "EMC")) { // copy to global rtapi_strxcpy(emc_nmlfile, inistring->c_str()); } else { // not found, use default } - for (t = 0; t < EMCMOT_MAX_JOINTS; t++) { + for (int t = 0; t < EMCMOT_MAX_JOINTS; t++) { jogPol[t] = 1; // set to default - snprintf(displayString, sizeof(displayString), "JOINT_%d", t); - auto inistring = inifile.Find("JOGGING_POLARITY", displayString); - if (inistring && 1 == sscanf(inistring->c_str(), "%d", &i) && i == 0) { + auto inival = inifile.findSInt("JOGGING_POLARITY", fmt::format("JOINT_{}", t)); + if (inival && *inival == 0) { // it read as 0, so override default jogPol[t] = 0; } } - if (auto inistring = inifile.Find("LINEAR_UNITS", "DISPLAY")) { - if (*inistring == "AUTO") { - linearUnitConversion = LINEAR_UNITS_AUTO; - } else if (*inistring == "INCH") { - linearUnitConversion = LINEAR_UNITS_INCH; - } else if (*inistring == "MM") { - linearUnitConversion = LINEAR_UNITS_MM; - } else if (*inistring == "CM") { - linearUnitConversion = LINEAR_UNITS_CM; - } - } else { - // not found, leave default alone - } - - if (auto inistring = inifile.Find("ANGULAR_UNITS", "DISPLAY")) { - if (*inistring == "AUTO") { - angularUnitConversion = ANGULAR_UNITS_AUTO; - } else if (*inistring == "DEG") { - angularUnitConversion = ANGULAR_UNITS_DEG; - } else if (*inistring == "RAD") { - angularUnitConversion = ANGULAR_UNITS_RAD; - } else if (*inistring == "GRAD") { - angularUnitConversion = ANGULAR_UNITS_GRAD; - } - } else { - // not found, leave default alone - } + if (auto inival = mapLinearUnits(inifile, "LINEAR_UNITS", "DISPLAY")) { + linearUnitConversion = *inival; + } // else not found, leave default alone - // close it - inifile.Close(); + if (auto inival = mapAngularUnits(inifile, "ANGULAR_UNITS", "DISPLAY")) { + angularUnitConversion = *inival; + } // else not found, leave default alone return 0; } diff --git a/src/emc/usr_intf/shcom.hh b/src/emc/usr_intf/shcom.hh index c7867df6c99..755cfb255dc 100644 --- a/src/emc/usr_intf/shcom.hh +++ b/src/emc/usr_intf/shcom.hh @@ -20,8 +20,10 @@ #include #include // INCH_PER_MM +#include #include "nml_intf/emc_nml.hh" #include "libnml/nml/nml_oi.hh" // NML_ERROR_LEN +#include "unitenum.hh" static inline bool CLOSE(double a, double b, double eps) { @@ -37,22 +39,7 @@ static inline bool CLOSE(double a, double b, double eps) #define JOGTELEOP 0 #define JOGJOINT 1 -enum LINEAR_UNIT_CONVERSION { - LINEAR_UNITS_CUSTOM = 1, - LINEAR_UNITS_AUTO, - LINEAR_UNITS_MM, - LINEAR_UNITS_INCH, - LINEAR_UNITS_CM -}; extern LINEAR_UNIT_CONVERSION linearUnitConversion; - -enum ANGULAR_UNIT_CONVERSION { - ANGULAR_UNITS_CUSTOM = 1, - ANGULAR_UNITS_AUTO, - ANGULAR_UNITS_DEG, - ANGULAR_UNITS_RAD, - ANGULAR_UNITS_GRAD -}; extern ANGULAR_UNIT_CONVERSION angularUnitConversion; // the current command numbers, set up updateStatus(), used in main() diff --git a/src/emc/usr_intf/unitenum.hh b/src/emc/usr_intf/unitenum.hh new file mode 100644 index 00000000000..b3b1a85bad9 --- /dev/null +++ b/src/emc/usr_intf/unitenum.hh @@ -0,0 +1,25 @@ +#ifndef __LINUXCNC_USR_INTF_UNITENUM_HH +#define __LINUXCNC_USR_INTF_UNITENUM_HH + +// Removed definition from shcom.hh and moved to here so that mapini.{cc,hh} +// can use them without pulling in other stuff. +// Removed a local redefinition copy from halui.cc that now can use this +// include instead. + +enum LINEAR_UNIT_CONVERSION : int { + LINEAR_UNITS_CUSTOM = 1, + LINEAR_UNITS_AUTO, + LINEAR_UNITS_MM, + LINEAR_UNITS_INCH, + LINEAR_UNITS_CM +}; + +enum ANGULAR_UNIT_CONVERSION : int { + ANGULAR_UNITS_CUSTOM = 1, + ANGULAR_UNITS_AUTO, + ANGULAR_UNITS_DEG, + ANGULAR_UNITS_RAD, + ANGULAR_UNITS_GRAD +}; + +#endif diff --git a/src/hal/user_comps/Submakefile b/src/hal/user_comps/Submakefile index 661d5259e4d..42a52579cee 100644 --- a/src/hal/user_comps/Submakefile +++ b/src/hal/user_comps/Submakefile @@ -73,7 +73,7 @@ ifdef HAVE_LIBUSB10 XHC_HB04_SRC = hal/user_comps/xhc-hb04.cc USERSRCS += $(XHC_HB04_SRC) $(call TOOBJSDEPS, $(XHC_HB04_SRC)) : EXTRAFLAGS += $(LIBUSB10_CFLAGS) -../bin/xhc-hb04: $(call TOOBJS, $(XHC_HB04_SRC)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.0 +../bin/xhc-hb04: $(call TOOBJS, $(XHC_HB04_SRC)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) $(LDFLAGS) -o $@ $^ -lm $(LIBUSB10_LIBS) TARGETS += ../bin/xhc-hb04 diff --git a/src/hal/user_comps/mb2hal/Submakefile b/src/hal/user_comps/mb2hal/Submakefile index 111d5a1aef8..e10b0498c0c 100644 --- a/src/hal/user_comps/mb2hal/Submakefile +++ b/src/hal/user_comps/mb2hal/Submakefile @@ -19,7 +19,7 @@ $(call TOOBJS, $(MB2HAL_SRCS)): Makefile.inc USERSRCS += $(MB2HAL_SRCS) # This is how the binaries are linked. -../bin/mb2hal: $(call TOOBJS, $(MB2HAL_SRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.0 +../bin/mb2hal: $(call TOOBJS, $(MB2HAL_SRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) $(Q)@$(CC) $(LDFLAGS) -o $@ $^ $(MB2HAL_LDFLAGS) diff --git a/src/hal/user_comps/mb2hal/mb2hal.c b/src/hal/user_comps/mb2hal/mb2hal.c index bb605b24cab..ce1480a88a5 100644 --- a/src/hal/user_comps/mb2hal/mb2hal.c +++ b/src/hal/user_comps/mb2hal/mb2hal.c @@ -48,12 +48,6 @@ int main(int argc, char **argv) return -1; } - gbl.ini_file_ptr = fopen(gbl.ini_file_path, "r"); - if (gbl.ini_file_ptr == NULL) { - ERR(gbl.init_dbg, "Unable to open INI file [%s]", gbl.ini_file_path); - return -1; - } - if (parse_ini_file() != 0) { ERR(gbl.init_dbg, "Unable to parse INI file [%s]", gbl.ini_file_path); goto QUIT_CLEANUP; diff --git a/src/hal/user_comps/mb2hal/mb2hal.h b/src/hal/user_comps/mb2hal/mb2hal.h index 74574b3c81e..409c9562005 100644 --- a/src/hal/user_comps/mb2hal/mb2hal.h +++ b/src/hal/user_comps/mb2hal/mb2hal.h @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,7 +13,7 @@ #include #include #include -#include "libnml/inifile/inifile.h" +#include #include @@ -136,8 +137,7 @@ typedef struct { //Reduce functions parameters using this common global structure. typedef struct { //INI config file - FILE *ini_file_ptr; - char *ini_file_path; + const char *ini_file_path; //INI config, common section int init_dbg; int version; diff --git a/src/hal/user_comps/mb2hal/mb2hal_init.c b/src/hal/user_comps/mb2hal/mb2hal_init.c index ccd52748c6f..754f3ebfe7e 100644 --- a/src/hal/user_comps/mb2hal/mb2hal_init.c +++ b/src/hal/user_comps/mb2hal/mb2hal_init.c @@ -37,11 +37,6 @@ retCode parse_ini_file() char *fnct_name = "parse_ini_file"; int counter; - if (gbl.ini_file_ptr == NULL) { - ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); - return retERR; - } - if (parse_common_section() != retOK) { ERR(gbl.init_dbg, "parse_common_section failed"); return retERR; @@ -78,41 +73,33 @@ retCode parse_common_section() { char *fnct_name = "parse_common_section"; char *section = "MB2HAL_INIT", *tag; - const char *tmpstr; char tmpbuf[INI_MAX_LINELEN]; - if (gbl.ini_file_ptr == NULL) { - ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); - return retERR; - } - tag = "INIT_DEBUG"; //optional - iniFindInt(gbl.ini_file_ptr, tag, section, &gbl.init_dbg); + iniFindInt(gbl.ini_file_path, tag, section, &gbl.init_dbg); DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, gbl.init_dbg); tag = "VERSION"; //optional - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { int major, minor; - sscanf(tmpstr, "%d.%d", &major, &minor); + sscanf(tmpbuf, "%d.%d", &major, &minor); gbl.version = major*1000 + minor; } DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, gbl.version); tag = "HAL_MODULE_NAME"; //optional - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { - gbl.hal_mod_name = strdup(tmpstr); + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { + gbl.hal_mod_name = strdup(tmpbuf); } //else already initilizaed by default DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, gbl.hal_mod_name); tag = "SLOWDOWN"; //optional - iniFindDouble(gbl.ini_file_ptr, tag, section, &gbl.slowdown); + iniFindDouble(gbl.ini_file_path, tag, section, &gbl.slowdown); DBG(gbl.init_dbg, "[%s] [%s] [%0.3f]", section, tag, gbl.slowdown); tag = "TOTAL_TRANSACTIONS"; //required - if (iniFindInt(gbl.ini_file_ptr, tag, section, &gbl.tot_mb_tx) != 0) { + if (iniFindInt(gbl.ini_file_path, tag, section, &gbl.tot_mb_tx) != 0) { ERR(gbl.init_dbg, "required [%s] [%s] not found", section, tag); return retERR; } @@ -133,7 +120,7 @@ static retCode parse_pin_names(const char * const names_string, mb_tx_t * const int name_buf_size = NAME_ALLOC_SIZE; char **name_ptrs = malloc(sizeof(char *) * name_buf_size); /* FIXME This memory block is leaked */ - char *names = strndup(names_string,999942); + char *names = strdup(names_string); if(name_ptrs == NULL || names == NULL) { ERR(gbl.init_dbg, "Failed allocating memory"); @@ -176,14 +163,9 @@ retCode parse_transaction_section(const int mb_tx_num) char *fnct_name = "parse_transaction_section"; char section[40]; char *tag; - const char *tmpstr; char tmpbuf[INI_MAX_LINELEN]; mb_tx_t *this_mb_tx; - if (gbl.ini_file_ptr == NULL) { - ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); - return retERR; - } if (mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { ERR(gbl.init_dbg, "out of range"); return retERR; @@ -191,7 +173,7 @@ retCode parse_transaction_section(const int mb_tx_num) this_mb_tx = &gbl.mb_tx[mb_tx_num]; - if (gbl.ini_file_ptr == NULL || mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { + if (mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { ERR(gbl.init_dbg, "parameter error"); return retERR; } @@ -199,20 +181,19 @@ retCode parse_transaction_section(const int mb_tx_num) snprintf(section, sizeof(section)-1, "TRANSACTION_%02d", mb_tx_num); tag = "LINK_TYPE"; //required 1st time, then optional - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { - if (strcasecmp(tmpstr, "tcp") == retOK) { + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { + if (strcasecmp(tmpbuf, "tcp") == retOK) { this_mb_tx->cfg_link_type = linkTCP; - rtapi_strxcpy(this_mb_tx->cfg_link_type_str, tmpstr); + rtapi_strxcpy(this_mb_tx->cfg_link_type_str, tmpbuf); } - else if (strcasecmp(tmpstr, "serial") == retOK) { + else if (strcasecmp(tmpbuf, "serial") == retOK) { this_mb_tx->cfg_link_type = linkRTU; - rtapi_strxcpy(this_mb_tx->cfg_link_type_str, tmpstr); + rtapi_strxcpy(this_mb_tx->cfg_link_type_str, tmpbuf); } else { this_mb_tx->cfg_link_type = -1; rtapi_strxcpy(this_mb_tx->cfg_link_type_str, ""); - ERR(gbl.init_dbg, "[%s] [%s] [%s] is not valid", section, tag, tmpstr); + ERR(gbl.init_dbg, "[%s] [%s] [%s] is not valid", section, tag, tmpbuf); return retERR; } } @@ -244,7 +225,7 @@ retCode parse_transaction_section(const int mb_tx_num) } tag = "MB_SLAVE_ID"; //1st time required - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_tx_slave_id) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->mb_tx_slave_id) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->mb_tx_slave_id = gbl.mb_tx[mb_tx_num-1].mb_tx_slave_id; @@ -264,7 +245,7 @@ retCode parse_transaction_section(const int mb_tx_num) DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->mb_tx_slave_id); tag = "FIRST_ELEMENT"; //required - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_tx_1st_addr) != 0) { + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->mb_tx_1st_addr) != 0) { ERR(gbl.init_dbg, "required [%s] [%s] not found", section, tag); return retERR; } @@ -276,21 +257,20 @@ retCode parse_transaction_section(const int mb_tx_num) tag = "PIN_NAMES"; - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { - if(parse_pin_names(tmpstr, this_mb_tx) != retOK) + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { + if(parse_pin_names(tmpbuf, this_mb_tx) != retOK) { - ERR(gbl.init_dbg, "[%s] [%s] [%s] list format error", section, tag, tmpstr); + ERR(gbl.init_dbg, "[%s] [%s] [%s] list format error", section, tag, tmpbuf); return retERR; } - DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, tmpstr); + DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, tmpbuf); } else { this_mb_tx->mb_tx_names = NULL; } tag = "NELEMENTS"; //required - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_tx_nelem) != 0 && + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->mb_tx_nelem) != 0 && this_mb_tx->mb_tx_names == NULL) { ERR(gbl.init_dbg, "required [%s] [%s] or [%s] [PIN_NAMES] were not found", section, tag, section); return retERR; @@ -303,7 +283,7 @@ retCode parse_transaction_section(const int mb_tx_num) tag = "MAX_UPDATE_RATE"; //optional this_mb_tx->cfg_update_rate = 0; //default: 0=infinite - if (iniFindDouble(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_update_rate) != 0) { //not found + if (iniFindDouble(gbl.ini_file_path, tag, section, &this_mb_tx->cfg_update_rate) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->cfg_update_rate = gbl.mb_tx[mb_tx_num-1].cfg_update_rate; @@ -314,7 +294,7 @@ retCode parse_transaction_section(const int mb_tx_num) tag = "MB_RESPONSE_TIMEOUT_MS"; //optional this_mb_tx->mb_response_timeout_ms = MB2HAL_DEFAULT_MB_RESPONSE_TIMEOUT_MS; //default - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_response_timeout_ms) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->mb_response_timeout_ms) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->mb_response_timeout_ms = gbl.mb_tx[mb_tx_num-1].mb_response_timeout_ms; @@ -325,7 +305,7 @@ retCode parse_transaction_section(const int mb_tx_num) tag = "MB_BYTE_TIMEOUT_MS"; //optional this_mb_tx->mb_byte_timeout_ms = MB2HAL_DEFAULT_MB_BYTE_TIMEOUT_MS; //default - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->mb_byte_timeout_ms) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->mb_byte_timeout_ms) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->mb_byte_timeout_ms = gbl.mb_tx[mb_tx_num-1].mb_byte_timeout_ms; @@ -336,7 +316,7 @@ retCode parse_transaction_section(const int mb_tx_num) tag = "DEBUG"; //optional this_mb_tx->cfg_debug = debugERR; //default - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_debug) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->cfg_debug) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->cfg_debug = gbl.mb_tx[mb_tx_num-1].cfg_debug; @@ -346,19 +326,18 @@ retCode parse_transaction_section(const int mb_tx_num) DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_debug); tag = "MB_TX_CODE"; //required - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { int i; for (i=0 ; imb_tx_fnct = i; - strncpy(this_mb_tx->mb_tx_fnct_name, tmpstr, sizeof(this_mb_tx->mb_tx_fnct_name)-1); + rtapi_strxcpy(this_mb_tx->mb_tx_fnct_name, tmpbuf); break; } } mb_tx_fnct max = gbl.version<1001?mbtx_01_READ_COILS:mbtxMAX; if (this_mb_tx->mb_tx_fnct <= mbtxERR || this_mb_tx->mb_tx_fnct >= max) { - ERR(gbl.init_dbg, "[%s] [%s] [%s] out of range", section, tag, tmpstr); + ERR(gbl.init_dbg, "[%s] [%s] [%s] out of range", section, tag, tmpbuf); return retERR; } } @@ -369,9 +348,8 @@ retCode parse_transaction_section(const int mb_tx_num) DBG(gbl.init_dbg, "[%s] [%s] [%s] [%d]", section, tag, this_mb_tx->mb_tx_fnct_name, this_mb_tx->mb_tx_fnct); tag = "HAL_TX_NAME"; //optional - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { - strncpy(this_mb_tx->hal_tx_name, tmpstr, HAL_NAME_LEN); + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { + rtapi_strxcpy(this_mb_tx->hal_tx_name, tmpbuf); } else { snprintf(this_mb_tx->hal_tx_name, sizeof(this_mb_tx->hal_tx_name), "%02d", mb_tx_num); @@ -385,14 +363,9 @@ retCode parse_tcp_subsection(const char *section, const int mb_tx_num) { char *fnct_name="parse_tcp_subsection"; char *tag; - const char *tmpstr; char tmpbuf[INI_MAX_LINELEN]; mb_tx_t *this_mb_tx; - if (gbl.ini_file_ptr == NULL || section == NULL) { - ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); - return retERR; - } if (mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { ERR(gbl.init_dbg, "out of range"); return retERR; @@ -401,9 +374,8 @@ retCode parse_tcp_subsection(const char *section, const int mb_tx_num) this_mb_tx = &gbl.mb_tx[mb_tx_num]; tag = "TCP_IP"; //required 1st time, then optional - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { - strncpy(this_mb_tx->cfg_tcp_ip, tmpstr, sizeof(this_mb_tx->cfg_tcp_ip)-1); + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { + rtapi_strxcpy(this_mb_tx->cfg_tcp_ip, tmpbuf); } else { if (mb_tx_num > 0) { //previous value? @@ -426,7 +398,7 @@ retCode parse_tcp_subsection(const char *section, const int mb_tx_num) tag = "TCP_PORT"; //optional this_mb_tx->cfg_tcp_port = MB2HAL_DEFAULT_TCP_PORT; //default - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_tcp_port) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->cfg_tcp_port) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->cfg_tcp_port = gbl.mb_tx[mb_tx_num-1].cfg_tcp_port; @@ -442,12 +414,11 @@ retCode parse_serial_subsection(const char *section, const int mb_tx_num) { char *fnct_name="parse_serial_subsection"; char *tag; - const char *tmpstr; char tmpbuf[INI_MAX_LINELEN]; mb_tx_t *this_mb_tx; - if (gbl.ini_file_ptr == NULL || section == NULL) { - ERR(gbl.init_dbg, "gbl.ini_file_ptr NULL pointer"); + if (section == NULL) { + ERR(gbl.init_dbg, "section NULL pointer"); return retERR; } if (mb_tx_num < 0 || mb_tx_num > gbl.tot_mb_tx) { @@ -458,9 +429,8 @@ retCode parse_serial_subsection(const char *section, const int mb_tx_num) this_mb_tx = &gbl.mb_tx[mb_tx_num]; tag = "SERIAL_PORT"; //required 1st time - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { - strncpy(this_mb_tx->cfg_serial_device, tmpstr, sizeof(this_mb_tx->cfg_serial_device)-1); + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { + rtapi_strxcpy(this_mb_tx->cfg_serial_device, tmpbuf); } else { if (mb_tx_num > 0) { //previous value? @@ -482,7 +452,7 @@ retCode parse_serial_subsection(const char *section, const int mb_tx_num) DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, this_mb_tx->cfg_serial_device); tag = "SERIAL_BAUD"; //1st time required - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_baud) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->cfg_serial_baud) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->cfg_serial_baud = gbl.mb_tx[mb_tx_num-1].cfg_serial_baud; @@ -502,7 +472,7 @@ retCode parse_serial_subsection(const char *section, const int mb_tx_num) DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_serial_baud); tag = "SERIAL_BITS"; //1st time required - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_data_bit) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->cfg_serial_data_bit) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->cfg_serial_data_bit = gbl.mb_tx[mb_tx_num-1].cfg_serial_data_bit; @@ -526,9 +496,8 @@ retCode parse_serial_subsection(const char *section, const int mb_tx_num) DBG(gbl.init_dbg, "[%s] [%s] [%d]", section, tag, this_mb_tx->cfg_serial_data_bit); tag = "SERIAL_PARITY"; //required 1st time - tmpstr = iniFindString(gbl.ini_file_ptr, tag, section, tmpbuf, sizeof(tmpbuf)); - if (tmpstr != NULL) { - strncpy(this_mb_tx->cfg_serial_parity, tmpstr, sizeof(this_mb_tx->cfg_serial_parity)-1); + if (0 == iniFindString(gbl.ini_file_path, tag, section, tmpbuf, sizeof(tmpbuf))) { + rtapi_strxcpy(this_mb_tx->cfg_serial_parity, tmpbuf); } else { if (mb_tx_num > 0) { //previous value? @@ -554,7 +523,7 @@ retCode parse_serial_subsection(const char *section, const int mb_tx_num) DBG(gbl.init_dbg, "[%s] [%s] [%s]", section, tag, this_mb_tx->cfg_serial_parity); tag = "SERIAL_STOP"; //1st time required - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_stop_bit) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->cfg_serial_stop_bit) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->cfg_serial_stop_bit = gbl.mb_tx[mb_tx_num-1].cfg_serial_stop_bit; @@ -579,7 +548,7 @@ retCode parse_serial_subsection(const char *section, const int mb_tx_num) tag = "SERIAL_DELAY_MS"; //optional this_mb_tx->cfg_serial_delay_ms = 0; //default - if (iniFindInt(gbl.ini_file_ptr, tag, section, &this_mb_tx->cfg_serial_delay_ms) != 0) { //not found + if (iniFindInt(gbl.ini_file_path, tag, section, &this_mb_tx->cfg_serial_delay_ms) != 0) { //not found if (mb_tx_num > 0) { //previous value? if (strcasecmp(this_mb_tx->cfg_link_type_str, gbl.mb_tx[mb_tx_num-1].cfg_link_type_str) == 0) { this_mb_tx->cfg_serial_delay_ms = gbl.mb_tx[mb_tx_num-1].cfg_serial_delay_ms; diff --git a/src/hal/user_comps/vfdb_vfd/Submakefile b/src/hal/user_comps/vfdb_vfd/Submakefile index 84fc71985c0..9fad354ca7f 100644 --- a/src/hal/user_comps/vfdb_vfd/Submakefile +++ b/src/hal/user_comps/vfdb_vfd/Submakefile @@ -7,7 +7,7 @@ VFDB_LIBS = $(LIBMODBUS_LIBS) -lm $(call TOOBJSDEPS, $(VFDB_SRCS)) : EXTRAFLAGS += $(VFDB_CFLAGS) USERSRCS += $(VFDB_SRCS) -../bin/vfdb_vfd: $(call TOOBJS, $(VFDB_SRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.0 +../bin/vfdb_vfd: $(call TOOBJS, $(VFDB_SRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(VFDB_LIBS) diff --git a/src/hal/user_comps/vfdb_vfd/vfdb_vfd.c b/src/hal/user_comps/vfdb_vfd/vfdb_vfd.c index d744b3b06d3..553681e8e9a 100644 --- a/src/hal/user_comps/vfdb_vfd/vfdb_vfd.c +++ b/src/hal/user_comps/vfdb_vfd/vfdb_vfd.c @@ -61,7 +61,7 @@ #include #include #include -#include "libnml/inifile/inifile.h" +#include // command registers for DELTA VFD-B Inverter #define REG_COMMAND1 0x2000 // "Communication command" - start/stop, fwd/reverse, DC break, fault reset, panel override @@ -160,7 +160,6 @@ typedef struct params { int stopbits; char *progname; char *section; - FILE *fp; char *inifile; int reconnect_delay; modbus_t *ctx; @@ -191,7 +190,6 @@ static params_type param = { .stopbits = 1, .progname = "vfdb_vfd", .section = "VFD-B", - .fp = NULL, .inifile = NULL, .reconnect_delay = 1, .ctx = NULL, @@ -277,20 +275,19 @@ enum kwdresult {NAME_NOT_FOUND, KEYWORD_INVALID, KEYWORD_FOUND}; int findkwd(param_pointer p, const char *name, int *result, const char *keyword, int value, ...) { - const char *word; char wordbuf[INI_MAX_LINELEN]; va_list ap; const char *kwds[MAX_KWD], **s; int nargs = 0; - if ((word = iniFindString(p->fp, name, p->section, wordbuf, sizeof(wordbuf))) == NULL) + if (iniFindString(p->inifile, name, p->section, wordbuf, sizeof(wordbuf))) return NAME_NOT_FOUND; kwds[nargs++] = keyword; va_start(ap, value); while (keyword != NULL) { - if (!strcasecmp(word, keyword)) { + if (!strcasecmp(wordbuf, keyword)) { *result = value; va_end(ap); return KEYWORD_FOUND; @@ -301,7 +298,7 @@ int findkwd(param_pointer p, const char *name, int *result, const char *keyword, value = va_arg(ap, int); } fprintf(stderr, "%s: %s:[%s]%s: found '%s' - not one of: ", - p->progname, p->inifile, p->section, name, word); + p->progname, p->inifile, p->section, name, wordbuf); for (s = kwds; *s; s++) fprintf(stderr, "%s ", *s); fprintf(stderr, "\n"); @@ -311,41 +308,34 @@ int findkwd(param_pointer p, const char *name, int *result, const char *keyword, int read_ini(param_pointer p) { - const char *s; char sbuf[INI_MAX_LINELEN]; int value; - if ((p->fp = fopen(p->inifile,"r")) != NULL) { - if (!p->debug) - iniFindInt(p->fp, "DEBUG", p->section, &p->debug); - if (!p->modbus_debug) - iniFindInt(p->fp, "MODBUS_DEBUG", p->section, &p->modbus_debug); - iniFindInt(p->fp, "BITS", p->section, &p->bits); - iniFindInt(p->fp, "BAUD", p->section, &p->baud); - iniFindInt(p->fp, "STOPBITS", p->section, &p->stopbits); - iniFindInt(p->fp, "TARGET", p->section, &p->slave); - iniFindInt(p->fp, "POLLCYCLES", p->section, &p->pollcycles); - iniFindInt(p->fp, "RECONNECT_DELAY", p->section, &p->reconnect_delay); - - iniFindInt(p->fp, "MOTOR_HZ", p->section, &p->motor_hz); - iniFindInt(p->fp, "MOTOR_RPM", p->section, &p->motor_rpm); - - if ((s = iniFindString(p->fp, "DEVICE", p->section, sbuf, sizeof(sbuf)))) { - p->device = strdup(s); - } - value = p->parity; - if (findkwd(p, "PARITY", &value, - "even",'E', - "odd", 'O', - "none", 'N', - (void *)NULL) == KEYWORD_INVALID) - return -1; - p->parity = value; - } else { - fprintf(stderr, "%s:can not open INI file '%s'\n", - p->progname, p->inifile); - return -1; + if (!p->debug) + iniFindInt(p->inifile, "DEBUG", p->section, &p->debug); + if (!p->modbus_debug) + iniFindInt(p->inifile, "MODBUS_DEBUG", p->section, &p->modbus_debug); + iniFindInt(p->inifile, "BITS", p->section, &p->bits); + iniFindInt(p->inifile, "BAUD", p->section, &p->baud); + iniFindInt(p->inifile, "STOPBITS", p->section, &p->stopbits); + iniFindInt(p->inifile, "TARGET", p->section, &p->slave); + iniFindInt(p->inifile, "POLLCYCLES", p->section, &p->pollcycles); + iniFindInt(p->inifile, "RECONNECT_DELAY", p->section, &p->reconnect_delay); + + iniFindInt(p->inifile, "MOTOR_HZ", p->section, &p->motor_hz); + iniFindInt(p->inifile, "MOTOR_RPM", p->section, &p->motor_rpm); + + if (0 == iniFindString(p->inifile, "DEVICE", p->section, sbuf, sizeof(sbuf))) { + p->device = strdup(sbuf); } + value = p->parity; + if (findkwd(p, "PARITY", &value, + "even",'E', + "odd", 'O', + "none", 'N', + (void *)NULL) == KEYWORD_INVALID) + return -1; + p->parity = value; return 0; } diff --git a/src/hal/user_comps/vfs11_vfd/Submakefile b/src/hal/user_comps/vfs11_vfd/Submakefile index 09bdd282a46..a8c17ae2dd7 100644 --- a/src/hal/user_comps/vfs11_vfd/Submakefile +++ b/src/hal/user_comps/vfs11_vfd/Submakefile @@ -8,7 +8,7 @@ VFS11_LIBS = $(LIBMODBUS_LIBS) $(call TOOBJSDEPS, $(VFS11_SRCS)) : EXTRAFLAGS += $(VFS11_CFLAGS) USERSRCS += $(VFS11_SRCS) -../bin/vfs11_vfd: $(call TOOBJS, $(VFS11_SRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.0 +../bin/vfs11_vfd: $(call TOOBJS, $(VFS11_SRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(VFS11_LIBS) diff --git a/src/hal/user_comps/vfs11_vfd/vfs11_vfd.c b/src/hal/user_comps/vfs11_vfd/vfs11_vfd.c index 7a1aadab2ff..c1b9699bdad 100644 --- a/src/hal/user_comps/vfs11_vfd/vfs11_vfd.c +++ b/src/hal/user_comps/vfs11_vfd/vfs11_vfd.c @@ -62,7 +62,7 @@ #include #include #include -#include "libnml/inifile/inifile.h" +#include /* * VFS-11 parameters: @@ -272,7 +272,6 @@ typedef struct params { int tcp_portno; char *progname; char *section; - FILE *fp; char *inifile; int reconnect_delay; modbus_t *ctx; @@ -312,7 +311,6 @@ static params_type param = { .tcp_portno = 1502, // MODBUS_TCP_DEFAULT_PORT (502) would require root privileges .progname = "vfs11_vfd", .section = "VFS11", - .fp = NULL, .inifile = NULL, .reconnect_delay = 1, .ctx = NULL, @@ -397,20 +395,19 @@ enum kwdresult {NAME_NOT_FOUND, KEYWORD_INVALID, KEYWORD_FOUND}; int findkwd(param_pointer p, const char *name, int *result, const char *keyword, int value, ...) { - const char *word; char wordbuf[INI_MAX_LINELEN]; va_list ap; const char *kwds[MAX_KWD], **s; int nargs = 0; - if ((word = iniFindString(p->fp, name, p->section, wordbuf, sizeof(wordbuf))) == NULL) + if (iniFindString(p->inifile, name, p->section, wordbuf, sizeof(wordbuf))) return NAME_NOT_FOUND; kwds[nargs++] = keyword; va_start(ap, value); while (keyword != NULL) { - if (!strcasecmp(word, keyword)) { + if (!strcasecmp(wordbuf, keyword)) { *result = value; va_end(ap); return KEYWORD_FOUND; @@ -421,7 +418,7 @@ int findkwd(param_pointer p, const char *name, int *result, const char *keyword, value = va_arg(ap, int); } fprintf(stderr, "%s: %s:[%s]%s: found '%s' - not one of: ", - p->progname, p->inifile, p->section, name, word); + p->progname, p->inifile, p->section, name, wordbuf); for (s = kwds; *s; s++) fprintf(stderr, "%s ", *s); fprintf(stderr, "\n"); @@ -431,81 +428,74 @@ int findkwd(param_pointer p, const char *name, int *result, const char *keyword, int read_ini(param_pointer p) { - const char *s; char sbuf[INI_MAX_LINELEN]; double f; int value; - if ((p->fp = fopen(p->inifile,"r")) != NULL) { - if (!p->debug) - iniFindInt(p->fp, "DEBUG", p->section, &p->debug); - if (!p->modbus_debug) - iniFindInt(p->fp, "MODBUS_DEBUG", p->section, &p->modbus_debug); - iniFindInt(p->fp, "BITS", p->section, &p->bits); - iniFindInt(p->fp, "BAUD", p->section, &p->baud); - iniFindInt(p->fp, "STOPBITS", p->section, &p->stopbits); - iniFindInt(p->fp, "TARGET", p->section, &p->slave); - iniFindInt(p->fp, "POLLCYCLES", p->section, &p->pollcycles); - iniFindInt(p->fp, "PORT", p->section, &p->tcp_portno); - iniFindInt(p->fp, "RECONNECT_DELAY", p->section, &p->reconnect_delay); - - if ((s = iniFindString(p->fp, "TCPDEST", p->section, sbuf, sizeof(sbuf)))) { - p->tcp_destip = strdup(s); - } - if ((s = iniFindString(p->fp, "DEVICE", p->section, sbuf, sizeof(sbuf)))) { - p->device = strdup(s); - } - if (iniFindDouble(p->fp, "RESPONSE_TIMEOUT", p->section, &f)) { - p->response_timeout.tv_sec = (int) f; - p->response_timeout.tv_usec = (f-p->response_timeout.tv_sec) * 1000000; - } - if (iniFindDouble(p->fp, "BYTE_TIMEOUT", p->section, &f)) { - p->byte_timeout.tv_sec = (int) f; - p->byte_timeout.tv_usec = (f-p->byte_timeout.tv_sec) * 1000000; - } - value = p->parity; - if (findkwd(p, "PARITY", &value, - "even",'E', - "odd", 'O', - "none", 'N', - (void *)NULL) == KEYWORD_INVALID) - return -1; - p->parity = value; + if (!p->debug) + iniFindInt(p->inifile, "DEBUG", p->section, &p->debug); + if (!p->modbus_debug) + iniFindInt(p->inifile, "MODBUS_DEBUG", p->section, &p->modbus_debug); + iniFindInt(p->inifile, "BITS", p->section, &p->bits); + iniFindInt(p->inifile, "BAUD", p->section, &p->baud); + iniFindInt(p->inifile, "STOPBITS", p->section, &p->stopbits); + iniFindInt(p->inifile, "TARGET", p->section, &p->slave); + iniFindInt(p->inifile, "POLLCYCLES", p->section, &p->pollcycles); + iniFindInt(p->inifile, "PORT", p->section, &p->tcp_portno); + iniFindInt(p->inifile, "RECONNECT_DELAY", p->section, &p->reconnect_delay); + + if (0 == iniFindString(p->inifile, "TCPDEST", p->section, sbuf, sizeof(sbuf))) { + p->tcp_destip = strdup(sbuf); + } + if (0 == iniFindString(p->inifile, "DEVICE", p->section, sbuf, sizeof(sbuf))) { + p->device = strdup(sbuf); + } + if (iniFindDouble(p->inifile, "RESPONSE_TIMEOUT", p->section, &f)) { + p->response_timeout.tv_sec = (int) f; + p->response_timeout.tv_usec = (f-p->response_timeout.tv_sec) * 1000000; + } + if (iniFindDouble(p->inifile, "BYTE_TIMEOUT", p->section, &f)) { + p->byte_timeout.tv_sec = (int) f; + p->byte_timeout.tv_usec = (f-p->byte_timeout.tv_sec) * 1000000; + } + value = p->parity; + if (findkwd(p, "PARITY", &value, + "even",'E', + "odd", 'O', + "none", 'N', + (void *)NULL) == KEYWORD_INVALID) + return -1; + p->parity = value; #ifdef MODBUS_RTU_RTS_UP - if (findkwd(p, "RTS_MODE", &p->rts_mode, - "up", MODBUS_RTU_RTS_UP, - "down", MODBUS_RTU_RTS_DOWN, - "none", MODBUS_RTU_RTS_NONE, - (void *)NULL) == KEYWORD_INVALID) - return -1; + if (findkwd(p, "RTS_MODE", &p->rts_mode, + "up", MODBUS_RTU_RTS_UP, + "down", MODBUS_RTU_RTS_DOWN, + "none", MODBUS_RTU_RTS_NONE, + (void *)NULL) == KEYWORD_INVALID) + return -1; #else - if (iniFindString(p->fp, "RTS_MODE", p->section, sbuf, sizeof(sbuf)) != NULL) { - fprintf(stderr,"%s: warning - the RTS_MODE feature is not available with the installed libmodbus version (%s).\n" - "to enable it, uninstall libmodbus-dev and rebuild with " - "libmodbus built http://github.com/stephane/libmodbus:master .\n", - LIBMODBUS_VERSION_STRING, p->progname); - } + if (iniFindString(p->inifile, "RTS_MODE", p->section, sbuf, sizeof(sbuf)) != NULL) { + fprintf(stderr,"%s: warning - the RTS_MODE feature is not available with the installed libmodbus version (%s).\n" + "to enable it, uninstall libmodbus-dev and rebuild with " + "libmodbus built http://github.com/stephane/libmodbus:master .\n", + LIBMODBUS_VERSION_STRING, p->progname); + } #endif - if (findkwd(p,"SERIAL_MODE", &p->serial_mode, - "rs232", MODBUS_RTU_RS232, - "rs485", MODBUS_RTU_RS485, - (void *)NULL) == KEYWORD_INVALID) - return -1; - - if (findkwd(p, "TYPE", &p->type, - "rtu", TYPE_RTU, - "tcpserver", TYPE_TCP_SERVER, - "tcpclient", TYPE_TCP_CLIENT, - (void *)NULL) == NAME_NOT_FOUND) { - fprintf(stderr, "%s: missing required TYPE in section %s\n", - p->progname, p->section); - return -1; - } - } else { - fprintf(stderr, "%s:can not open inifile '%s'\n", - p->progname, p->inifile); - return -1; + if (findkwd(p,"SERIAL_MODE", &p->serial_mode, + "rs232", MODBUS_RTU_RS232, + "rs485", MODBUS_RTU_RS485, + (void *)NULL) == KEYWORD_INVALID) + return -1; + + if (findkwd(p, "TYPE", &p->type, + "rtu", TYPE_RTU, + "tcpserver", TYPE_TCP_SERVER, + "tcpclient", TYPE_TCP_CLIENT, + (void *)NULL) == NAME_NOT_FOUND) { + fprintf(stderr, "%s: missing required TYPE in section %s\n", + p->progname, p->section); + return -1; } return 0; } diff --git a/src/hal/user_comps/xhc-hb04.cc b/src/hal/user_comps/xhc-hb04.cc index 1fe84aae8d4..5c37a013052 100644 --- a/src/hal/user_comps/xhc-hb04.cc +++ b/src/hal/user_comps/xhc-hb04.cc @@ -33,10 +33,12 @@ #include #include -#include "libnml/inifile/inifile.hh" +#include #include "config.h" +using namespace linuxcnc; + const char *modname = "xhc-hb04"; int hal_comp_id; const char *section = "XHC-HB04"; @@ -667,15 +669,15 @@ static int hal_setup() int read_ini_file(char *filename) { - IniFile iniFile; + IniFile iniFile(filename); int nb_buttons = 0; - if (!iniFile.Open(filename)) { + if (!iniFile) { fprintf(stderr, "%s: Could not open configuration file: %s\n", modname, filename); return -1; } - while (auto bt = iniFile.Find("BUTTON", section, nb_buttons+1)) { + while (auto bt = iniFile.findString(nb_buttons+1, "BUTTON", section)) { if (nb_buttons >= NB_MAX_BUTTONS) break; if (sscanf(bt->c_str(), "%x:%255s", &xhc.buttons[nb_buttons].code, xhc.buttons[nb_buttons].pin_name) !=2 ) { fprintf(stderr, "%s: syntax error\n", bt->c_str()); diff --git a/src/hal/utils/Submakefile b/src/hal/utils/Submakefile index 8f19c566a4b..12d60b7b9de 100644 --- a/src/hal/utils/Submakefile +++ b/src/hal/utils/Submakefile @@ -8,12 +8,12 @@ USERSRCS += $(sort $(HALCMDSRCS) $(HALSHSRCS)) $(call TOOBJSDEPS, $(HALSHSRCS)) : EXTRAFLAGS = -fPIC $(call TOOBJSDEPS, hal/utils/halsh.c) : EXTRAFLAGS += $(TCL_CFLAGS) -../tcl/hal.so: $(call TOOBJS, $(HALSHSRCS)) ../lib/liblinuxcncini.so.0 ../lib/liblinuxcnchal.so.0 +../tcl/hal.so: $(call TOOBJS, $(HALSHSRCS)) ../lib/liblinuxcncini.so.1 ../lib/liblinuxcnchal.so.0 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) $(LDFLAGS) -shared $^ $(TCL_LIBS) -o $@ TARGETS += ../tcl/hal.so -../bin/halcmd: $(call TOOBJS, $(HALCMDSRCS)) ../lib/liblinuxcncini.so.0 ../lib/liblinuxcnchal.so.0 +../bin/halcmd: $(call TOOBJS, $(HALCMDSRCS)) ../lib/liblinuxcncini.so.1 ../lib/liblinuxcnchal.so.0 $(ECHO) Linking $(notdir $@) $(Q)$(CXX) $(LDFLAGS) -o $@ $^ $(READLINE_LIBS) TARGETS += ../bin/halcmd diff --git a/src/hal/utils/halcmd.c b/src/hal/utils/halcmd.c index fd0792f2fd4..2899e62992a 100644 --- a/src/hal/utils/halcmd.c +++ b/src/hal/utils/halcmd.c @@ -46,8 +46,8 @@ #include #ifndef NO_INI -#include "libnml/inifile/inifile.h" /* iniFindString() from libnml */ -FILE *halcmd_inifile = NULL; +#include +const char *halcmd_inifile = NULL; #endif #include @@ -773,14 +773,15 @@ static int replace_vars(char *source_str, char *dest_str, int max_chars, char ** return -7; strncpy(var, varP, next_delim); var[next_delim]='\0'; + replacement = ini_buf; if ( strlen(sec) > 0 ) { /* get value from INI file */ - replacement = (char *) iniFindString(halcmd_inifile, var, sec, - ini_buf, sizeof(ini_buf)); + if (iniFindString(halcmd_inifile, var, sec, ini_buf, sizeof(ini_buf))) + replacement = NULL; } else { /* no section specified */ - replacement = (char *) iniFindString(halcmd_inifile, var, NULL, - ini_buf, sizeof(ini_buf)); + if (iniFindString(halcmd_inifile, var, NULL, ini_buf, sizeof(ini_buf))) + replacement = NULL; } if (replacement==NULL) { *detail = info; diff --git a/src/hal/utils/halcmd.h b/src/hal/utils/halcmd.h index fe61a05f0a8..237c0bfe4e1 100644 --- a/src/hal/utils/halcmd.h +++ b/src/hal/utils/halcmd.h @@ -110,7 +110,7 @@ struct halcmd_command { extern struct halcmd_command halcmd_commands[]; extern int halcmd_ncommands; -extern FILE *halcmd_inifile; +extern const char *halcmd_inifile; #define MAX_TOK 32 #define MAX_CMD_LEN 1024 diff --git a/src/hal/utils/halcmd_main.c b/src/hal/utils/halcmd_main.c index e82d8fff8e8..0e0bf552b45 100644 --- a/src/hal/utils/halcmd_main.c +++ b/src/hal/utils/halcmd_main.c @@ -192,17 +192,11 @@ int main(int argc, char **argv) if (halcmd_inifile == NULL) { /* it's the first -i (ignore repeats) */ /* there is a following arg, and it's not an option */ - filename = optarg; - halcmd_inifile = fopen(filename, "r"); + halcmd_inifile = strdup(optarg); if (halcmd_inifile == NULL) { - fprintf(stderr, - "Could not open INI file '%s'\n", - filename); + fprintf(stderr, "Could not open INI file '%s'\n", optarg); exit(-1); } - /* make sure file is closed on exec() */ - fd = fileno(halcmd_inifile); - fcntl(fd, F_SETFD, FD_CLOEXEC); } break; #endif /* NO_INI */ diff --git a/src/libnml/inifile/Submakefile b/src/libnml/inifile/Submakefile deleted file mode 100644 index dcd2a817a43..00000000000 --- a/src/libnml/inifile/Submakefile +++ /dev/null @@ -1,19 +0,0 @@ - -LIBINISRCS := libnml/inifile/inifile.cc -$(call TOOBJSDEPS, $(LIBINISRCS)) : EXTRAFLAGS=-fPIC - -INIFILESRCS := libnml/inifile/inivar.cc - -USERSRCS += $(INIFILESRCS) $(LIBINISRCS) -TARGETS += ../lib/liblinuxcncini.so ../lib/liblinuxcncini.so.0 - -../lib/liblinuxcncini.so.0: $(call TOOBJS,$(LIBINISRCS)) - $(ECHO) Creating shared library $(notdir $@) - @mkdir -p ../lib - @rm -f $@ - $(Q)$(CXX) $(LDFLAGS) -Wl,-soname,$(notdir $@) -shared -o $@ $^ - -../bin/inivar: $(call TOOBJS, $(INIFILESRCS)) ../lib/liblinuxcncini.so.0 - $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) $(LDFLAGS) -o $@ $^ -TARGETS += ../bin/inivar diff --git a/src/libnml/inifile/inifile.cc b/src/libnml/inifile/inifile.cc deleted file mode 100644 index 420ccf5fd6b..00000000000 --- a/src/libnml/inifile/inifile.cc +++ /dev/null @@ -1,632 +0,0 @@ -/***************************************************************************** - * Description: inifile.cc - * C++ INI file reader - * - * Derived from a work by Fred Proctor & Will Shackleford - * - * Author: - * License: GPL Version 2 - * System: Linux - * - * Copyright (c) 2004 All rights reserved. - * - * Last change: - *****************************************************************************/ - -#include /* FILE *, fopen(), fclose(), NULL */ -#include -#include /* strstr() */ -#include - - -#include -#include "inifile.hh" - -constexpr int MAX_EXTEND_LINES = 20; - - -IniFile::IniFile(int _errMask, FILE *_fp) : fp(_fp), errMask(_errMask) -{ - if(fp) - LockFile(); -} - - -/*! Opens the file for reading. If a file was already open, it is closed - and the new one opened. - - @return true on success, false on failure */ -bool -IniFile::Open(const char *file) -{ - char path[LINELEN] = ""; - - if(IsOpen()) Close(); - - TildeExpansion(file, path, sizeof(path)); - - if((fp = fopen(path, "r")) == NULL) - return(false); - - owned = true; - - if(!LockFile()) - return(false); - - return(true); -} - - -/*! Closes the file descriptor.. - - @return true on success, false on failure */ -bool -IniFile::Close() -{ - int rVal = 0; - - if(fp != NULL){ - lock.l_type = F_UNLCK; - fcntl(fileno(fp), F_SETLKW, &lock); - - if(owned) - rVal = fclose(fp); - - fp = NULL; - } - - return(rVal == 0); -} - - -IniFile::ErrorCode -IniFile::Find(int *result, StrIntPair *pPair, - const char *tag, const char *section, int num, int *lineno) -{ - auto pStr = Find(tag, section, num); - if(!pStr){ - // We really need an ErrorCode return from Find() and should be passing - // in a buffer. Just pick a suitable ErrorCode for now. - if (lineno) - *lineno = 0; - return(ERR_TAG_NOT_FOUND); - } - - int tmp; - if(sscanf(pStr->c_str(), "%i", &tmp) == 1){ - *result = tmp; - if (lineno) - *lineno = lineNo; - return(ERR_NONE); - } - - while(pPair->pStr != NULL){ - if(strcasecmp(pStr->c_str(), pPair->pStr) == 0){ - *result = pPair->value; - if (lineno) - *lineno = lineNo; - return(ERR_NONE); - } - pPair++; - } - - ThrowException(ERR_CONVERSION); - return(ERR_CONVERSION); -} - - -IniFile::ErrorCode -IniFile::Find(double *result, StrDoublePair *pPair, - const char *tag, const char *section, int num, int *lineno) -{ - auto pStr = Find(tag, section, num); - if(!pStr){ - // We really need an ErrorCode return from Find() and should be passing - // in a buffer. Just pick a suitable ErrorCode for now. - if (lineno) - *lineno = 0; - return(ERR_TAG_NOT_FOUND); - } - - double tmp; - if(sscanf(pStr->c_str(), "%lf", &tmp) == 1){ - if (lineno) - *lineno = lineNo; - *result = tmp; - if (lineno) - *lineno = lineNo; - return(ERR_NONE); - } - - while(pPair->pStr != NULL){ - if(strcasecmp(pStr->c_str(), pPair->pStr) == 0){ - *result = pPair->value; - if (lineno) - *lineno = lineNo; - return(ERR_NONE); - } - pPair++; - } - - ThrowException(ERR_CONVERSION); - return(ERR_CONVERSION); -} - - -/*! Finds the nth tag in section. - - @param tag Entry in the ini file to find. - - @param section The section to look for the tag. - - @param num (optionally) the Nth occurrence of the tag. - - @return pointer to the variable after the '=' delimiter, or @c NULL if not - found */ -std::optional -IniFile::Find(const char *_tag, const char *_section, int _num, int *lineno) -{ - char line[LINELEN + 2] = ""; /* 1 for newline, 1 for NULL */ - - char eline [(LINELEN + 2) * (MAX_EXTEND_LINES + 1)]; - char* elineptr; - char* elinenext = eline; - int extend_ct = 0; - - if (!_tag) { - fprintf(stderr, "IniFile: error: Tag is not provided\n"); - return std::nullopt; - } - - // For exceptions. - lineNo = 0; - tag = _tag; - section = _section; - num = _num; - - /* check valid file */ - if(!CheckIfOpen()) - return std::nullopt; - - /* start from beginning */ - rewind(fp); - - /* check for section first-- if it's non-NULL, then position file at - line after [section] */ - if (section) { - char bracketSection[LINELEN]; - snprintf(bracketSection, sizeof(bracketSection), "[%s]", section); - - /* find [section], and position fp just after it */ - while (!feof(fp)) { - - if (NULL == fgets(line, LINELEN + 1, fp)) { - /* got to end of file without finding it */ - ThrowException(ERR_SECTION_NOT_FOUND); - return std::nullopt; - } - - if (HasInvalidLineEnding(line)) { - ThrowException(ERR_CONVERSION); - return std::nullopt; - } - - /* got a line */ - lineNo++; - - /* strip off newline */ - int newLinePos = static_cast(strlen(line)) - 1; /* newline is on back from 0 */ - if (newLinePos < 0) { - newLinePos = 0; - } - if (line[newLinePos] == '\n') { - line[newLinePos] = 0; /* make the newline 0 */ - } - - const char* nonWhite = SkipWhite(line); - if (!nonWhite) { - /* blank line-- skip */ - continue; - } - - /* not a blank line, and nonwhite is first char */ - if (strncmp(bracketSection, nonWhite, strlen(bracketSection)) != 0){ - /* not on this line */ - continue; - } - - /* it matches-- fp is now set up for search on tag */ - break; - } - } - - while (!feof(fp)) { - /* check for end of file */ - if (NULL == fgets(line, LINELEN + 1, fp)) { - /* got to end of file without finding it */ - ThrowException(ERR_TAG_NOT_FOUND); - return std::nullopt; - } - - if (HasInvalidLineEnding(line)) { - ThrowException(ERR_CONVERSION); - return std::nullopt; - } - - /* got a line */ - lineNo++; - - /* strip off newline */ - int newLinePos = static_cast(strlen(line)) - 1; /* newline is on back from 0 */ - if (newLinePos < 0) { - newLinePos = 0; - } - if (line[newLinePos] == '\n') { - line[newLinePos] = 0; /* make the newline 0 */ - } - // honor backslash (\) as line-end escape - if (newLinePos > 0 && line[newLinePos-1] == '\\') { - newLinePos = newLinePos-1; - line[newLinePos] = 0; - if (!extend_ct) { - elineptr = eline; //first time - strncpy(elineptr,line,newLinePos); - elinenext = elineptr + newLinePos; - } else { - strncpy(elinenext,line,newLinePos); - elinenext = elinenext + newLinePos; - } - *elinenext = 0; - extend_ct++; - if (extend_ct > MAX_EXTEND_LINES) { - fprintf(stderr, - "INIFILE lineno=%u:Too many backslash line extends (limit=%d)\n", - lineNo, MAX_EXTEND_LINES); - ThrowException(ERR_OVER_EXTENDED); - return std::nullopt; - } - continue; // get next line to extend - } else { - if (extend_ct) { - strncpy(elinenext,line,newLinePos); - elinenext = elinenext + newLinePos; - *elinenext = 0; - } - } - if (!extend_ct) { - elineptr = line; - } - extend_ct = 0; - - /* skip leading whitespace */ - char* nonWhite = SkipWhite(elineptr); - if (!nonWhite) { - /* blank line-- skip */ - continue; - } - - /* check for '[' char-- if so, it's a section tag, and we're out - of our section */ - if (NULL != section && nonWhite[0] == '[') { - ThrowException(ERR_TAG_NOT_FOUND); - return std::nullopt; - } - - const std::size_t tagLength = strlen(tag); - if (strncmp(tag, nonWhite, tagLength) != 0) { - /* not on this line */ - continue; - } - - /* it matches the first part of the string-- if whitespace or = is - next char then call it a match */ - const char tagEnd = nonWhite[tagLength]; - if (tagEnd == ' ' || tagEnd == '\r' || tagEnd == '\t' - || tagEnd == '\n' || tagEnd == '=') { - /* it matches-- return string after =, or NULL */ - if (--_num > 0) { - /* Not looking for this one, so skip it... */ - continue; - } - nonWhite += tagLength; - char* valueString = AfterEqual(nonWhite); - /* Eliminate white space at the end of a line also. */ - if (!valueString) { - ThrowException(ERR_TAG_NOT_FOUND); - return std::nullopt; - } - char* endValueString = valueString + strlen(valueString) - 1; - while (*endValueString == ' ' || *endValueString == '\t' - || *endValueString == '\r') { - *endValueString = 0; - endValueString--; - } - if (lineno) - *lineno = lineNo; - return std::string(valueString); - } - /* else continue */ - } - - ThrowException(ERR_TAG_NOT_FOUND); - return std::nullopt; -} - -IniFile::ErrorCode -IniFile::Find(std::string *s, const char *_tag, const char *_section, int _num) -{ - auto tmp = Find(_tag, _section, _num); - if(!tmp) - return ERR_TAG_NOT_FOUND; // can't distinguish errors, ugh - - *s = *tmp; - - return(ERR_NONE); -} - -std::optional -IniFile::FindString(char *dest, size_t n, const char *_tag, const char *_section, int _num, int *lineno) -{ - auto res = Find(_tag, _section, _num, lineno); - if(!res) - return std::nullopt; - int r = snprintf(dest, n, "%s", res->c_str()); - if(r < 0 || (size_t)r >= n) { - ThrowException(ERR_CONVERSION); - return std::nullopt; - } - return dest; -} - -std::optional -IniFile::FindPath(char *dest, size_t n, const char *_tag, const char *_section, int _num, int *lineno) -{ - auto res = Find(_tag, _section, _num, lineno); - if(!res) - return std::nullopt; - if(TildeExpansion(res->c_str(), dest, n)) { - return std::nullopt; - } - return dest; -} - -bool -IniFile::CheckIfOpen() -{ - if(IsOpen()) - return(true); - - ThrowException(ERR_NOT_OPEN); - - return(false); -} - - -bool -IniFile::LockFile() -{ - lock.l_type = F_RDLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - - if(fcntl(fileno(fp), F_SETLK, &lock) == -1){ - if(owned) - fclose(fp); - - fp = NULL; - return(false); - } - - return(true); -} - - -/*! Expands the tilde to $(HOME) and concatenates file to it. If the first char - If file does not start with ~/, file will be copied into path as-is. - - @param the input filename - - @param pointer for returning the resulting expanded name - - */ -IniFile::ErrorCode -IniFile::TildeExpansion(const char *file, char *path, size_t size) -{ - int res = snprintf(path, size, "%s", file); - if(res < 0 || (size_t)res >= size) - return ERR_CONVERSION; - - if (strlen(file) < 2 || !(file[0] == '~' && file[1] == '/')) { - /* no tilde expansion required, or unsupported - tilde expansion type requested */ - return ERR_NONE; - } - - const char *home = getenv("HOME"); - if (!home) { - ThrowException(ERR_CONVERSION); - return ERR_CONVERSION; - } - - res = snprintf(path, size, "%s%s", home, file + 1); - if(res < 0 || (size_t)res >= size) { - ThrowException(ERR_CONVERSION); - return ERR_CONVERSION; - } - - return ERR_NONE; -} - -void -IniFile::ThrowException(ErrorCode errCode) -{ - if(errCode & errMask){ - exception.errCode = errCode; - exception.tag = tag; - exception.section = section; - exception.num = num; - exception.lineNo = lineNo; - throw(exception); - } -} - -/*! - * @brief Checks if a line has an invalid line ending. - * - * This function examines each character in the given line and reports warnings - * or errors related to line endings. It detects DOS-style line endings (CRLF) - * and ambiguous carriage returns. - * @param line To be checked for invalid line endings. - * @return True if an invalid line ending is found, false otherwise. - */ -bool IniFile::HasInvalidLineEnding(const char *line) -{ - if (!line) - return false; - - for (; *line; line++) { - if (*line == '\r') { - char c = line[1]; - if (c == '\n' || c == '\0') { - if (!lineEndingReported) { - fprintf(stderr, "IniFile: warning: File contains DOS-style line endings.\n"); - lineEndingReported = true; - } - continue; - } - fprintf(stderr, "IniFile: error: File contains ambiguous carriage returns\n"); - return true; - } - } - return false; -} - -/*! Ignoring any tabs, spaces or other white spaces, finds the first - character after the '=' delimiter. - - @param string Pointer to the tag - - @return NULL or pointer to first non-white char after the delimiter - - Called By: find() and section() only. */ -char* -IniFile::AfterEqual(char *string) -{ - while (*string != '\0' && *string != '=') - ++string; - - if (*string == '=') { - do { - ++string; - if (*string == '\0') - return nullptr; - } while (*string == ' ' || *string == '\t' || *string == '\r' || *string == '\n'); - return string; - } - return nullptr; -} - - -/*! Finds the first non-white character on a new line and returns a - pointer. Ignores any line that starts with a comment char i.e. a ';' or - '#'. - - @return NULL if not found or a valid pointer. - - Called By: find() and section() only. */ -char* -IniFile::SkipWhite(char *string) -{ - while (*string != ';' && *string != '#' && *string != '\0') { - if (*string != ' ' && *string != '\t' && *string != '\r' && *string != '\n') { - return string; - } - string++; - } - return nullptr; -} - - -void -IniFile::Exception::Print(FILE *fp) -{ - const char *msg; - - switch(errCode){ - case ERR_NONE: - msg = "ERR_NONE"; - break; - - case ERR_NOT_OPEN: - msg = "ERR_NOT_OPEN"; - break; - - case ERR_SECTION_NOT_FOUND: - msg = "ERR_SECTION_NOT_FOUND"; - break; - - case ERR_TAG_NOT_FOUND: - msg = "ERR_TAG_NOT_FOUND"; - break; - - case ERR_CONVERSION: - msg = "ERR_CONVERSION"; - break; - - case ERR_LIMITS: - msg = "ERR_LIMITS"; - break; - - case ERR_OVER_EXTENDED: - msg = "ERR_OVER_EXTENDED"; - break; - - default: - msg = "UNKNOWN"; - } - - fprintf(fp, "INIFILE: %s, section=%s, tag=%s, num=%d, lineNo=%u\n", - msg, section, tag, num, lineNo); -} - - -extern "C" const char * -iniFindString(FILE *fp, const char *tag, const char *section, - char *buf, size_t bufsize) -{ - IniFile f(false, fp); - auto result = f.FindString(buf, bufsize, tag, section); - if (!result) return nullptr; - return *result; -} - -extern "C" const char * -iniFind(FILE *fp, const char *tag, const char *section) -{ - // Deprecated: This function uses static storage and is not reentrant. - // Use iniFindString() instead for thread-safe operation. - static char result_storage[LINELEN]; - return iniFindString(fp, tag, section, result_storage, sizeof(result_storage)); -} - -extern "C" int -iniFindInt(FILE *fp, const char *tag, const char *section, int *result) -{ - IniFile f(false, fp); - return(f.Find(result, tag, section)); -} - -extern "C" int -iniFindDouble(FILE *fp, const char *tag, const char *section, double *result) -{ - IniFile f(false, fp); - return(f.Find(result, tag, section)); -} - -extern "C" int -TildeExpansion(const char *file, char *path, size_t size) -{ - static IniFile f; - return !f.TildeExpansion(file, path, size); -} diff --git a/src/libnml/inifile/inifile.h b/src/libnml/inifile/inifile.h deleted file mode 100644 index d0f31535c7b..00000000000 --- a/src/libnml/inifile/inifile.h +++ /dev/null @@ -1,42 +0,0 @@ -// Header for C-linkage apis in liblinuxcncini -// Copyright (C) 2012 Jeff Epler -// -// 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 2 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, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -#ifndef LINUXCNC_INIFILE_H -#define LINUXCNC_INIFILE_H - -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - -// Recommended buffer size for iniFindString() -#define INI_MAX_LINELEN 256 - -extern const char *iniFind(FILE *fp, const char *tag, const char *section) - __attribute__((deprecated("use iniFindString() instead"))); -extern const char *iniFindString(FILE *fp, const char *tag, const char *section, - char *buf, size_t bufsize); -extern int iniFindInt(FILE *fp, const char *tag, const char *section, int *result); -extern int iniFindDouble(FILE *fp, const char *tag, const char *section, double *result); -extern int TildeExpansion(const char *file, char *path, size_t size); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/libnml/inifile/inifile.hh b/src/libnml/inifile/inifile.hh deleted file mode 100644 index 5c42e29185a..00000000000 --- a/src/libnml/inifile/inifile.hh +++ /dev/null @@ -1,174 +0,0 @@ -/******************************************************************** -* Description: inifile.hh -* Declarations for INI file format functions -* -* Derived from a work by Fred Proctor & Will Shackleford -* -* Author: -* License: GPL Version 2 -* System: Linux -* -* Copyright (c) 2004 All rights reserved. -* -* Last change: -********************************************************************/ - -#ifndef INIFILE_HH -#define INIFILE_HH - -#include "inifile.h" -#include -#include - -#ifndef __cplusplus -#warning Inclusion of from C programs is deprecated. Include instead. -#endif - -#ifdef __cplusplus -#include -#include -class IniFile { -public: - enum ErrorCode { - ERR_NONE = 0x00, - ERR_NOT_OPEN = 0x01, - ERR_SECTION_NOT_FOUND = 0x02, - ERR_TAG_NOT_FOUND = 0x04, - ERR_CONVERSION = 0x08, - ERR_LIMITS = 0x10, - ERR_OVER_EXTENDED = 0x20, - }; - - class Exception { - public: - ErrorCode errCode; - const char * tag; - const char * section; - int num; - unsigned int lineNo; - - void Print(FILE *fp=stderr); - }; - - - explicit IniFile(int errMask = 0, FILE *fp = nullptr); - ~IniFile(){ Close(); } - - bool Open(const char *file); - bool Close(); - bool IsOpen(){ return(fp != nullptr); } - - std::optional Find(const char *tag, const char *section = nullptr, - int num = 1, int *lineno = nullptr); - - template - ErrorCode Find(T *result, T min, T max, - const char *tag,const char *section, - int num=1); - - template - ErrorCode Find(T *result, - const char *tag,const char *section, - int num=1); - - ErrorCode Find(std::string *s, - const char *tag,const char *section, - int num=1); - - std::optional FindString(char *dest, size_t n, - const char *tag, const char *section = nullptr, - int num = 1, int *lineno = nullptr); - std::optional FindPath(char *dest, size_t n, - const char *tag, const char *section = nullptr, - int num = 1, int *lineno = nullptr); - void EnableExceptions(int _errMask){ - errMask = _errMask; - } - - ErrorCode TildeExpansion(const char *file, char *path, - size_t n); - -protected: - struct StrIntPair { - const char *pStr; - int value; - }; - - struct StrDoublePair { - const char *pStr; - double value; - }; - - - ErrorCode Find(double *result, StrDoublePair *, - const char *tag, const char *section = nullptr, - int num = 1, int *lineno = nullptr); - ErrorCode Find(int *result, StrIntPair *, - const char *tag, const char *section = nullptr, - int num = 1, int *lineno = nullptr); - - -private: - FILE *fp; - struct flock lock{}; - bool owned{false}; - - Exception exception{}; - int errMask; - - unsigned int lineNo{}; - const char * tag{}; - const char * section{}; - int num{}; - bool lineEndingReported{false}; - - bool CheckIfOpen(); - bool LockFile(); - bool HasInvalidLineEnding(const char *line); - void ThrowException(ErrorCode); - char *AfterEqual(char *string); - char *SkipWhite(char *string); -}; - -template -IniFile::ErrorCode IniFile::Find(T *result, T min, T max, - const char *_tag,const char *_section, - int _num) -{ - ErrorCode errCode; - T tmp; - if((errCode = Find(&tmp, _tag, _section, _num)) != ERR_NONE) - return(errCode); - - if((tmp > max) || (tmp < min)) { - ThrowException(ERR_LIMITS); - return(ERR_LIMITS); - } - - *result = tmp; - - return(ERR_NONE); -} - -template -IniFile::ErrorCode IniFile::Find(T *result, const char *_tag, - const char *_section, int _num) -{ - ErrorCode errCode; - std::string tmp; - if((errCode = Find(&tmp, _tag, _section, _num)) != ERR_NONE) - return(errCode); - - try { - *result = boost::lexical_cast(tmp); - } catch (boost::bad_lexical_cast &) { - ThrowException(ERR_CONVERSION); - return(ERR_CONVERSION); - } - - return(ERR_NONE); -} -#endif - - -#endif diff --git a/src/libnml/inifile/inivar.cc b/src/libnml/inifile/inivar.cc deleted file mode 100644 index 2a4fded0db5..00000000000 --- a/src/libnml/inifile/inivar.cc +++ /dev/null @@ -1,132 +0,0 @@ -/******************************************************************** -* Description: inivar.cc -* prints to stdout the INI file result of a variable-in-section -* search, useful for scripts that want to pick things out of INI files. -* -* syntax: inivar -var {-sec
} {-ini } -* -* Uses emc.ini as default. needs to be supplied. If
-* is omitted, first instance of will be looked for in any -* section. Otherwise only a match of the variable in
will -* be returned. -* -* Derived from a work by Fred Proctor & Will Shackleford -* -* Author: -* License: GPL Version 2 -* System: Linux -* -* Copyright (c) 2004 All rights reserved. -* -* Last change: -********************************************************************/ - -#include /* printf(), fprintf(), FILE, fopen(),*/ -#include /* exit() */ -#include /* strcmp(), strcpy() */ -#include - -#include "inifile.hh" - - -int main(int argc, char *argv[]) -{ - int num = 1; - const char *variable = nullptr; - const char *section = nullptr; - const char *path = "emc.ini"; - int retval; - bool tildeExpand = false; - - /* process command line args, indexing argv[] from [1] */ - for (int t = 1; t < argc; t++) { - if (!strcmp(argv[t], "-ini")) { - if (t == argc - 1) { - /* no arg following -ini, so abort */ - fprintf(stderr, - "%s: INI file not specified after -ini\n", argv[0]); - exit(-1); - } else { - path = argv[t+1]; - t++; /* step over following arg */ - } - } else if (!strcmp(argv[t], "-var")) { - if (t == argc - 1) { - /* no arg following -var, so abort */ - fprintf(stderr, - "%s: variable name not specified after -var\n", argv[0]); - exit(-1); - } else { - variable = argv[t+1]; - t++; /* step over following arg */ - } - } else if (!strcmp(argv[t], "-sec")) { - if (t == argc - 1) { - /* no arg following -sec, so abort */ - fprintf(stderr, - "%s: section name not specified after -sec\n", argv[0]); - exit(-1); - } else { - section = argv[t+1]; - t++; /* step over following arg */ - } - } else if (!strcmp(argv[t], "-num")) { - if (t == argc - 1) { - /* no arg following -num, so abort */ - fprintf(stderr, - "%s: occurrence number not specified after -num\n", argv[0]); - exit(-1); - } else { - char *endPtr; - errno = 0; - long result = strtol(argv[t + 1], &endPtr, 10); - if (errno || *endPtr != '\0' || result < 0 || result > INT_MAX) { - fprintf(stderr, - "%s: invalid number after -num\n", argv[0]); - exit(-1); - } - num = static_cast(result); - t++; /* step over following arg */ - } - } else if (!strcmp(argv[t], "-tildeexpand")) { - tildeExpand = !tildeExpand; - } else { - /* invalid argument */ - fprintf(stderr, - "%s: -var [-tildeexpand] [-sec
] [-num ] [-ini ]\n", - argv[0]); - exit(-1); - } - } - - /* check that variable was supplied */ - if (!variable) { - fprintf(stderr, "%s: no variable supplied\n", argv[0]); - exit(-1); - } - - IniFile iniFile; - /* open the INI file */ - iniFile.Open(path); - if (!iniFile.IsOpen()) { - fprintf(stderr, "%s: can't open %s\n", argv[0], path); - exit(-1); - } - - auto iniString = iniFile.Find(variable, section, num); - if (iniString) { - if (tildeExpand) { - char expanded[PATH_MAX]; - iniFile.TildeExpansion(iniString->c_str(), expanded, sizeof(expanded)); - printf("%s\n", expanded); - } else { - printf("%s\n", iniString->c_str()); - } - retval = 0; - } else { - fprintf(stderr, "Can not find -sec %s -var %s -num %i \n", section, variable, num); - retval = 1; - } - - exit(retval); -} diff --git a/src/libnml/inifile/meson.build b/src/libnml/inifile/meson.build deleted file mode 100644 index 72ed982b800..00000000000 --- a/src/libnml/inifile/meson.build +++ /dev/null @@ -1,8 +0,0 @@ -inifile_srcs = files([ - 'inivar.cc', - 'inifile.cc', -]) -inifile_inc = include_directories([ - '.', -]) - diff --git a/src/libnml/rcs/rcs_print.hh b/src/libnml/rcs/rcs_print.hh index 6d2f81d898a..1a104b554ed 100644 --- a/src/libnml/rcs/rcs_print.hh +++ b/src/libnml/rcs/rcs_print.hh @@ -127,7 +127,7 @@ extern "C" { #define PRINT_ALL_SOCKET_REQUESTS 0x10000000 #define PRINT_EVERYTHING 0xFFFFFFFF /* 4294967295 */ #ifdef __cplusplus -enum RCS_PRINT_DESTINATION_TYPE { +enum RCS_PRINT_DESTINATION_TYPE : int { #else typedef enum { #endif diff --git a/tests/inifile/README b/tests/inifile/README index 8236667d727..38ecb541ad7 100644 --- a/tests/inifile/README +++ b/tests/inifile/README @@ -1,4 +1,5 @@ -Tests for the library `liblinuxcncini`. +Tests for the library `liblinuxcncini.so.1`. -This is the library created from the files `src/libnml/inifile/inifile.*`. To -simplify the testing we utilize the application `inivar`. +This is the library created from the files `src/emc/ini/inifile.*`. +The application `inivar` is used for compatibility. The new utility +`inivalue` is tested in a separate extensive test script. diff --git a/tests/inifile/comments/expected b/tests/inifile/comments/expected index 8ac35c677db..d51f7c967b6 100644 --- a/tests/inifile/comments/expected +++ b/tests/inifile/comments/expected @@ -1,4 +1,4 @@ value1 -value2 ; inline comment +value2 value3 -value4 # another inline comment +value4 diff --git a/tests/inifile/ini_api_c/expected b/tests/inifile/ini_api_c/expected new file mode 100644 index 00000000000..97242ba0620 --- /dev/null +++ b/tests/inifile/ini_api_c/expected @@ -0,0 +1,82 @@ ++++ Running test program for 'initest' +++ +--- test integer/unsigned value +42 +-42 +42 +xtest.ini:7: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +42 +-42 +42 +-42 +42 +xtest.ini:8: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +42 +-42 +42 +-42 +42 +xtest.ini:9: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +42 +-42 +42 +-42 +42 +xtest.ini:10: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +42 +-42 +--- test real value +42 +-42 +--- test boolean value +true +false +true +false +true +false +true +false +true +false +true +false +true +false +--- test bad integer/unsigned value +xtest.ini:14: error: Invalid signed integer [SECTION]VARBadNum='BadInt42' +iniFindSInt: Entry not found +xtest.ini:14: error: Invalid unsigned integer [SECTION]VARBadNum='BadInt42' +iniFindUInt: Entry not found +xtest.ini:14: error: Invalid floating point [SECTION]VARBadNum='BadInt42' +iniFindDouble: Entry not found +--- test warn integer/unsigned trailing context +xtest.ini:15: warning: Trailing character(s) in signed integer conversion ([SECTION]VARWarnNum='42Warn') +42 +xtest.ini:15: warning: Trailing character(s) in unsigned integer conversion ([SECTION]VARWarnNum='42Warn') +42 +xtest.ini:15: warning: Trailing character(s) in floating point conversion ([SECTION]VARWarnNum='42Warn') +42 +--- test bad boolean value +xtest.ini:13: error: Invalid boolean value [SECTION]VARBadBool='BadBool' +iniFindBool: Entry not found +--- test string value +BadInt42 +42Warn +--- test missing value +iniFindString: Entry not found +iniFindSInt: Entry not found +iniFindUInt: Entry not found +iniFindBool: Entry not found +iniFindInt: Entry not found +--- test missing section +value to check the rest is missing +iniFindString: Entry not found +iniFindSInt: Entry not found +iniFindUInt: Entry not found +iniFindBool: Entry not found +iniFindInt: Entry not found ++++ all done +++ diff --git a/tests/inifile/ini_api_c/initest.c b/tests/inifile/ini_api_c/initest.c new file mode 100644 index 00000000000..d71f5a3e213 --- /dev/null +++ b/tests/inifile/ini_api_c/initest.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include + +#include + +void myerror(const char *pfx, int err) +{ + fprintf(stderr, "%s: ", pfx); + switch(err) { + case -EINVAL: fprintf(stderr, "Invalid argument or ini-file"); break; + case -ENOSPC: fprintf(stderr, "Buffer too small"); break; + case -ENOENT: fprintf(stderr, "Entry not found"); break; + default: fprintf(stderr, "Unknown error: %d", err); break; + } + fprintf(stderr, "\n"); +} + +int main(int argc, char *argv[]) +{ + int lose = 0; + int optc; + const char *var = NULL; + const char *sec = NULL; + char typ = 's'; + + while(-1 != (optc = getopt(argc, argv, "v:s:t:"))) { + switch(optc) { + case 'v': + var = strdup(optarg); + break; + case 's': + sec = strdup(optarg); + break; + case 't': + switch(*optarg) { + case 'b': + case 'i': + case 'n': + case 'u': + case 'r': + case 's': + typ = *optarg; + break; + default: + fprintf(stderr, "Invalid type specifier '%c'\n", isprint(*optarg & 0xff) ? *optarg : '?'); + lose++; + break; + } + break; + default: + lose++; + break; + } + } + + if(optind >= argc) { + fprintf(stderr, "Missing ini file\n"); + lose++; + } + + if(lose) + return 1; + + int rv; + char buf[PATH_MAX]; + double r; + int n; + rtapi_s64 i; + rtapi_u64 u; + bool b; + + switch(typ) { + case 'b': + rv = iniFindBool(argv[optind], var, sec, &b); + if(!rv) { + printf("%s\n", b ? "true" : "false"); + } else { + myerror("iniFindBool", rv); + return -rv; + } + break; + case 'n': + rv = iniFindInt(argv[optind], var, sec, &n); + if(!rv) { + printf("%d\n", n); + } else { + myerror("iniFindInt", rv); + return -rv; + } + break; + case 'i': + rv = iniFindSInt(argv[optind], var, sec, &i); + if(!rv) { + printf("%ld\n", i); + } else { + myerror("iniFindSInt", rv); + return -rv; + } + break; + case 'u': + rv = iniFindUInt(argv[optind], var, sec, &u); + if(!rv) { + printf("%lu\n", u); + } else { + myerror("iniFindUInt", rv); + return -rv; + } + break; + case 'r': + rv = iniFindDouble(argv[optind], var, sec, &r); + if(!rv) { + printf("%.15lg\n", r); + } else { + myerror("iniFindDouble", rv); + return -rv; + } + break; + case 's': + rv = iniFindString(argv[optind], var, sec, buf, sizeof(buf)); + if(!rv) { + printf("%s\n", buf); + } else { + myerror("iniFindString", rv); + return -rv; + } + break; + default: + return 1; + } + + return 0; +} +// vim: ts=4 sw=4 diff --git a/tests/inifile/ini_api_c/test.sh b/tests/inifile/ini_api_c/test.sh new file mode 100755 index 00000000000..5285886b20a --- /dev/null +++ b/tests/inifile/ini_api_c/test.sh @@ -0,0 +1,95 @@ +#!/bin/sh +FLGS=( -O2 -Wall -Wextra -Werror -I"${HEADERS}" -DULAPI -L"${LIBDIR}" ) +gcc "${FLGS[@]}" -o initest initest.c -llinuxcncini || { echo "Failed compile"; exit 1; } + +[ -x ./initest ] || { echo "initest program not present or executable"; exit 1; } + +r() { echo "$@" >> result; } +f() { echo "***" "$@" "failed" >> result; } +t() { echo "***" "$@" "did not fail" >> result; } +tst() { ./initest "$@" xtest.ini >> result 2>&1; } + +true > result +r "+++ Running test program for 'initest' +++" + +( echo "[SECTION]" + echo "VAR='value to check the rest is missing'" + echo "VARdp=42" + echo "VARxp=0x2a" + echo "VARop=0o52" + echo "VARbp=0b101010" + echo "VARdm=-42" + echo "VARxm=-0x2a" + echo "VARom=-0o52" + echo "VARbm=-0b101010" + echo "VARrp=+4.2e1" + echo "VARrm=-4.2e1" + echo "VARBadBool=BadBool" + echo "VARBadNum=BadInt42" + echo "VARWarnNum=42Warn" + echo "VARBt1=TRUE" + echo "VARBt2=true" + echo "VARBt3=True" + echo "VARBt4=yes" + echo "VARBt5=YeS" + echo "VARBt6=1" + echo "VARBt7=oN" + echo "VARBf1=FALSE" + echo "VARBf2=false" + echo "VARBf3=FalSe" + echo "VARBf4=no" + echo "VARBf5=nO" + echo "VARBf6=0" + echo "VARBf7=oFf" ) > xtest.ini +r "--- test integer/unsigned value" +for i in d x o b; do + tst -v"VAR${i}p" -ti || f "Integer VAR${i}p variable" + tst -v"VAR${i}m" -ti || f "Integer VAR${i}m variable" + tst -v"VAR${i}p" -tu || f "Unsigned VAR${i}p variable" + tst -v"VAR${i}m" -tu || f "Unsigned VAR${i}m variable" + tst -v"VAR${i}p" -tn || f "Integer VAR${i}p variable" + tst -v"VAR${i}m" -tn || f "Integer VAR${i}m variable" +done +r "--- test real value" +tst -vVARrp -tr || f "Real variable" +tst -vVARrm -tr || f "Real variable" +r "--- test boolean value" +for i in 1 2 3 4 5 6 7; do + tst -v"VARBt${i}" -tb || f "Boolean variable VARBt${i}" + tst -v"VARBf${i}" -tb || f "Boolean variable VARBf${i}" +done + +r "--- test bad integer/unsigned value" +tst -v"VARBadNum" -ti && t "Bad integer VARBadNum" +tst -v"VARBadNum" -tu && t "Bad unsigned VARBadNum" +tst -v"VARBadNum" -tr && t "Bad real VARBadNum" +r "--- test warn integer/unsigned trailing context" +tst -v"VARWarnNum" -ti || f "Bad integer VARWarnNum" +tst -v"VARWarnNum" -tu || f "Bad unsigned VARWarnNum" +tst -v"VARWarnNum" -tr || f "Bad real VARWarnNum" +r "--- test bad boolean value" +tst -v"VARBadBool" -tb && t "Bad boolean VARBadBool" + +r "--- test string value" +tst -v"VARBadNum" -ts || f "String VARBadNum" +tst -v"VARWarnNum" -ts || f "String VARWarnNum" + +r "--- test missing value" +tst -v"MissingVAR" -ts && t "MissingVAR string" +tst -v"MissingVAR" -ti && t "MissingVAR sint" +tst -v"MissingVAR" -tu && t "MissingVAR uint" +tst -v"MissingVAR" -tb && t "MissingVAR bool" +tst -v"MissingVAR" -tn && t "MissingVAR int" + +r "--- test missing section" +tst -v"VAR" -s"SECTION" -ts || f "Section string" +tst -v"VAR" -s"MISSING" -ts && t "Missing section string" +tst -v"VAR" -s"MISSING" -ti && t "Missing section sint" +tst -v"VAR" -s"MISSING" -tu && t "Missing section uint" +tst -v"VAR" -s"MISSING" -tb && t "Missing section bool" +tst -v"VAR" -s"MISSING" -tn && t "Missing section int" + +r "+++ all done +++" + +rm -f initest xtest.ini +exit 0 diff --git a/tests/inifile/inivalue/expected b/tests/inifile/inivalue/expected new file mode 100644 index 00000000000..00bfd48f4cc --- /dev/null +++ b/tests/inifile/inivalue/expected @@ -0,0 +1,243 @@ ++++ Running test program for 'inivalue' +++ +--- test ini-file does not exist +this_file_does_not_exist_and_will_never_be_here.ini: error: Cannot open ini-file (errno=2 (No such file or directory)) +--- test missing include filename (without blank) +xtest.ini:1: error: Missing filename after #INCLUDE +--- test missing include filename (with blank) +xtest.ini:1: error: Missing filename after #INCLUDE +--- test include file not found +this_file_does_not_exist.inc: error: Cannot open ini-file (errno=2 (No such file or directory)) +--- test include file recursion +xtest.inc:1: error: Include file recursion on loading 'xtest.ini' +--- test invalid section, missing ']' +xtest.ini:1: error: Invalid section. Missing ']' +--- test invalid section, no content +xtest.ini:1: error: Invalid section. No content between '[' and ']' +xtest.ini:1: error: Invalid section. No content between '[' and ']' +--- test invalid section, invalid identifier +xtest.ini:1: error: Invalid section '0SECTION'. Cannot start with a digit +xtest.ini:1: error: Invalid section 'xæøΓ₯z'. Identifier contains invalid character(s) +--- test duplicate section merge warning +xtest.ini:2: warning: Section 'SECTION' already exists. Merging... +val +--- test section trailing junk warning +xtest.ini:1: warning: Section header has trailing content, ignored +val +--- test invalid variable name missing '=' +xtest.ini:2: error: Invalid tag 'VAR'. Expected '=' after tag identifier +--- test invalid variable name missing identifier +xtest.ini:2: error: Invalid tag. Missing identifier before '=' +--- test invalid variable name missing '=' +xtest.ini:2: error: Invalid tag 'VAR x'. Expected '=' after tag identifier +--- test invalid variable name identifier +xtest.ini:2: error: Invalid tag '0VAR'. Tag identifiers cannot start with a digit +xtest.ini:2: error: Invalid tag name 'VΓ…R'. Identifier contains invalid character(s) +xtest.ini:2: error: Invalid tag name 'VAR x'. Identifier contains invalid character(s) +--- test invalid variable outside section +xtest.ini:1: error: Tag 'VAR' found without prior section definition +--- test continuation +val more val +--- test last line has continuation without newline +xtest.ini:2: error: Last line has a continuation +--- test last line has continuation with newline +xtest.ini:2: error: Last line has a continuation +--- test getting all sections +S1 +S2 +S3 +--- test get all variable of a name +s1 +s2a +s2b +s2c +s3 +--- test get all variable of a name from the 3'rd +s2b +s2c +s3 +--- test get all variable of a name from unexisting number +error: No instance 42 or more of '[]VAR' +--- test get the 2'nd variable of global name VAR +s2a +--- test get the 2'nd variable of name VAR in section +s2b +--- test get the unexisting number variable of name VAR in section +error: Cannot find instance 42 of '[S2]VAR' +--- test integer/unsigned value +42 +-42 +42 +xtest.ini:6: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +42 +-42 +42 +xtest.ini:7: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +42 +-42 +42 +xtest.ini:8: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +42 +-42 +42 +xtest.ini:9: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +--- test real value +4.200000000000000e+01 +-4.200000000000000e+01 +--- test boolean value +true +false +1 +0 +True +False +true +false +1 +0 +True +False +true +false +1 +0 +True +False +true +false +1 +0 +True +False +true +false +1 +0 +True +False +true +false +1 +0 +True +False +true +false +1 +0 +True +False +--- test bad integer/unsigned value +xtest.ini:13: error: Invalid signed integer [SECTION]VARBadNum='BadInt42' +error: Invalid integer []VARBadNum='BadInt42' or out of range [-9223372036854775808,9223372036854775807] +xtest.ini:13: error: Invalid unsigned integer [SECTION]VARBadNum='BadInt42' +error: Invalid unsigned []VARBadNum='BadInt42' or out of range [0,18446744073709551615] +xtest.ini:13: error: Invalid floating point [SECTION]VARBadNum='BadInt42' +error: Invalid real []VARBadNum='BadInt42' or out of range [-1.79769e+308,1.79769e+308] +--- test warn integer/unsigned trailing context +xtest.ini:14: warning: Trailing character(s) in signed integer conversion ([SECTION]VARWarnNum='42Warn') +42 +xtest.ini:14: warning: Trailing character(s) in unsigned integer conversion ([SECTION]VARWarnNum='42Warn') +42 +xtest.ini:14: warning: Trailing character(s) in floating point conversion ([SECTION]VARWarnNum='42Warn') +4.200000000000000e+01 +--- test bad boolean value +xtest.ini:12: error: Invalid boolean value [SECTION]VARBadBool='BadBool' +--- test bad range +error: Invalid integer range min '0' > max '-1' +error: Invalid unsigned range min '1' > max '0' +error: Invalid real range min '0' > max '-1' +--- test range tested value +error: Invalid integer []VARdm='-42' or out of range [-1,0] +-42 +xtest.ini:6: warning: Unsigned integer conversion detected a leading minus sign (-) +error: Invalid unsigned []VARdm='-42' or out of range [0,0] +42 +xtest.ini:6: warning: Unsigned integer conversion detected a leading minus sign (-) +18446744073709551574 +error: Invalid real []VARrp='+4.2e1' or out of range [8,9.999] +4.200000000000000e+01 +error: Invalid real []VARrm='-4.2e1' or out of range [-1.21,0] +-4.200000000000000e+01 +--- test tilde expansion without HOME +error: Tilde expansion failed on '~/xyz' +--- test tilde expansion with HOME +/we/control/home/xyz +--- reproduce ini-file content +ini-file reproduction success +--- comment removal processing +xtest.ini:3: error: Invalid section. Missing ']' +blabla +blabla +--- string processing single/double quote +foo +bar +--- string merging +foobar +barfoo +foofoo +--- string quote escapes +foo s' d" +bar d" s' +--- string bad quote escapes +xtest.ini:3: warning: Improper escape of ' quote, ignored +foo \" +xtest.ini:3: warning: Improper escape of ' quote, ignored +bar ' +--- junk between string merge +xtest.ini:2: error: Expected single or double quote, got 'b' at position 7 of the value +xtest.ini:2: error: Expected single or double quote, got 'b' at position 7 of the value +--- escape fail at end-of-string +xtest.ini:2: error: Missing terminating " quote +--- string continuations escaped newlines +some +string +separated on +lines +--- embedded nul in values +xtest.ini:2: error: Embedded literal NUL character not supported +xtest.ini:2: error: Embedded literal NUL character not supported +xtest.ini:2: error: Embedded octal NUL character not supported +xtest.ini:2: error: Embedded hex NUL character not supported +xtest.ini:2: error: Embedded UTF-16 NUL character not supported +xtest.ini:2: error: Embedded UTF-32 NUL character not supported +--- escape invalid and improper hex +xtest.ini:2: error: Invalid hex 'YZ' in hex escape +xtest.ini:2: error: Improper hex escape +xtest.ini:2: error: Invalid hex 'Y ' in hex escape +xtest.ini:2: error: Invalid hex value 'VWZY' in UTF-16 escape +xtest.ini:2: error: Improper UTF-16 escape +xtest.ini:2: error: Invalid hex value 'VWZ ' in UTF-16 escape +xtest.ini:2: error: Invalid hex value 'vwxyijkl' in UTF-32 escape +xtest.ini:2: error: Improper UTF-32 escape +xtest.ini:2: error: Invalid hex value 'vwxyijk ' in UTF-32 escape +--- UTF-16 surrogates, sigh +xtest.ini:2: error: Missing low surrogate in UTF-16 +xtest.ini:2: error: Invalid high surrogate value uDC00 in UTF-16 +xtest.ini:2: error: Invalid low surrogate value uD800 in UTF-16 +xtest.ini:2: error: Invalid hex 'dc0 ' in UTF-16 low surrogate escape +πŸ˜€ +--- invalid code points +xtest.ini:2: error: Invalid code point U00110000 in UTF-32 escape +--- valid code points +AAAA +--- invalid UTF-8 +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +xtest.ini:2: error: Value contains invalid UTF-8 sequence +πŸ˜€ ++++ all done +++ diff --git a/tests/inifile/inivalue/test.sh b/tests/inifile/inivalue/test.sh new file mode 100755 index 00000000000..debe94ded4f --- /dev/null +++ b/tests/inifile/inivalue/test.sh @@ -0,0 +1,433 @@ +#!/bin/bash + +# We have many special echo statements with carefully planned good and bad +# escapes that are flagged. This directive disables the message and applies to +# the entire script. +# shellcheck disable=2028 +true + +INIVALUE=inivalue +[ -x ./inivalue ] && INIVALUE=./inivalue + +command -v "$INIVALUE" > /dev/null 2>&1 || { r "*** Missing inivalue executable"; exit 1; } + +r() { echo "$@" >> result; } +f() { echo "***" "$@" "failed" >> result; } +t() { echo "***" "$@" "did not fail" >> result; } +tst() { "$INIVALUE" "$@" xtest.ini >> result 2>&1; } + +true > result +r "+++ Running test program for 'inivalue' +++" + +r "--- test ini-file does not exist" +"$INIVALUE" --var=dontcare this_file_does_not_exist_and_will_never_be_here.ini >> result 2>&1 && t "Missing ini-file" + +r "--- test missing include filename (without blank)" +echo "#INCLUDE" > xtest.ini +tst --var=dontcare && t "Missing include" + +r "--- test missing include filename (with blank)" +echo "#INCLUDE " > xtest.ini +tst --var=dontcare && t "Missing include" + +r "--- test include file not found" +echo "#INCLUDE this_file_does_not_exist.inc" > xtest.ini +tst --var=dontcare && t "Nonexistent include file" + +r "--- test include file recursion" +echo "#INCLUDE xtest.inc" > xtest.ini +echo "#INCLUDE xtest.ini" > xtest.inc +tst --var=dontcare && t "Include recursion" +# we /could/ test max include depth, but that is a pain... + +r "--- test invalid section, missing ']'" +echo "[SECTION" > xtest.ini +tst --var=VAR && t "Invalid section" + +r "--- test invalid section, no content" +echo "[]" > xtest.ini +tst --var=VAR && t "Invalid section" +echo "[ ]" > xtest.ini +tst --var=VAR && t "Invalid section" + +r "--- test invalid section, invalid identifier" +echo "[0SECTION]" > xtest.ini +tst --var=VAR && t "Invalid section" +echo "[xæøΓ₯z]" > xtest.ini +tst --var=VAR && t "Invalid section" + +r "--- test duplicate section merge warning" +( echo "[SECTION]" + echo "[SECTION]" + echo "VAR=val" ) > xtest.ini +tst --var=VAR || f "Merging duplicate section" + +r "--- test section trailing junk warning" +( echo "[SECTION] This is junk" + echo "VAR=val" ) > xtest.ini +tst --var=VAR || f "Trailing section junk" + +r "--- test invalid variable name missing '='" +( echo "[SECTION]" + echo "VAR" ) > xtest.ini +tst --var=VAR && t "Invalid variable missing '='" + +r "--- test invalid variable name missing identifier" +( echo "[SECTION]" + echo " = value" ) > xtest.ini +tst --var=VAR && t "Invalid variable missing identifier" + +r "--- test invalid variable name missing '='" +( echo "[SECTION]" + echo "VAR x" ) > xtest.ini +tst --var=VAR && t "Invalid variable missing '='" + +r "--- test invalid variable name identifier" +( echo "[SECTION]" + echo "0VAR=val" ) > xtest.ini +tst --var=0VAR && t "Invalid variable name" +( echo "[SECTION]" + echo "VΓ…R=val" ) > xtest.ini +tst --var=VAR && t "Invalid variable name" +( echo "[SECTION]" + echo "VAR x=val" ) > xtest.ini +tst --var=VAR && t "Invalid variable name" + + +r "--- test invalid variable outside section" +echo "VAR=val" > xtest.ini +tst --var=VAR && t "Invalid variable outside section" + +r "--- test continuation" +( echo "[SECTION]" + echo "VAR=val \\" + echo " more val" ) > xtest.ini +tst --var=VAR || f "Variable/line continuation" + +r "--- test last line has continuation without newline" +( echo "[SECTION]" + echo -n "VAR=val \\" ) > xtest.ini +tst --var=VAR && t "Variable/line continuation" + +r "--- test last line has continuation with newline" +( echo "[SECTION]" + echo "VAR=val \\" ) > xtest.ini +tst --var=VAR && t "Variable/line continuation" + +r "--- test getting all sections" +( echo "[S1]" + echo "VAR=s1" + echo "[S2]" + echo "VAR=s2a" + echo "VAR=s2b" + echo "VAR=s2c" + echo "[S3]" + echo "VAR=s3" ) > xtest.ini +tst --sections || f "Getting sections" + +r "--- test get all variable of a name" +tst --var=VAR --all || f "Getting [*]VAR[1,-]" +r "--- test get all variable of a name from the 3'rd" +tst --var=VAR --all --num=3 || f "Getting [*]VAR[3,-]" +r "--- test get all variable of a name from unexisting number" +tst --var=VAR --all --num=42 && t "Getting [*]VAR[42,-]" + +r "--- test get the 2'nd variable of global name VAR" +tst --var=VAR --num=2 || f "Getting [*]VAR[2]" +r "--- test get the 2'nd variable of name VAR in section" +tst --var=VAR --num=2 --sec=S2 || f "Getting [S2]VAR[2]" +r "--- test get the unexisting number variable of name VAR in section" +tst --var=VAR --num=42 --sec=S2 && t "Getting [S2]VAR[42]" + +( echo "[SECTION]" + echo "VARdp=42" + echo "VARxp=0x2a" + echo "VARop=0o52" + echo "VARbp=0b101010" + echo "VARdm=-42" + echo "VARxm=-0x2a" + echo "VARom=-0o52" + echo "VARbm=-0b101010" + echo "VARrp=+4.2e1" + echo "VARrm=-4.2e1" + echo "VARBadBool=BadBool" + echo "VARBadNum=BadInt42" + echo "VARWarnNum=42Warn" + echo "VARBt1=TRUE" + echo "VARBt2=true" + echo "VARBt3=True" + echo "VARBt4=yes" + echo "VARBt5=YeS" + echo "VARBt6=1" + echo "VARBt7=oN" + echo "VARBf1=FALSE" + echo "VARBf2=false" + echo "VARBf3=FalSe" + echo "VARBf4=no" + echo "VARBf5=nO" + echo "VARBf6=0" + echo "VARBf7=oFf" ) > xtest.ini +r "--- test integer/unsigned value" +for i in d x o b; do + tst --var="VAR${i}p" --type=i || f "Integer VAR${i}p variable" + tst --var="VAR${i}m" --type=i || f "Integer VAR${i}m variable" + tst --var="VAR${i}p" --type=u || f "Unsigned VAR${i}p variable" + tst --var="VAR${i}m" --type=u || f "Unsigned VAR${i}m variable" +done +r "--- test real value" +tst --var=VARrp --type=r || f "Real variable" +tst --var=VARrm --type=r || f "Real variable" +r "--- test boolean value" +for i in 1 2 3 4 5 6 7; do + tst --var="VARBt${i}" --type=b || f "Boolean variable VARBt${i}" + tst --var="VARBf${i}" --type=b || f "Boolean variable VARBf${i}" + tst --var="VARBt${i}" --type=b -b || f "Boolean variable VARBt${i}" + tst --var="VARBf${i}" --type=b -b || f "Boolean variable VARBf${i}" + tst --var="VARBt${i}" --type=b -B || f "Boolean variable VARBt${i}" + tst --var="VARBf${i}" --type=b -B || f "Boolean variable VARBf${i}" +done + +r "--- test bad integer/unsigned value" +tst --var="VARBadNum" --type=i && t "Bad integer VARBadNum" +tst --var="VARBadNum" --type=u && t "Bad unsigned VARBadNum" +tst --var="VARBadNum" --type=r && t "Bad real VARBadNum" +r "--- test warn integer/unsigned trailing context" +tst --var="VARWarnNum" --type=i || f "Bad integer VARWarnNum" +tst --var="VARWarnNum" --type=u || f "Bad unsigned VARWarnNum" +tst --var="VARWarnNum" --type=r || f "Bad real VARWarnNum" +r "--- test bad boolean value" +tst --var="VARBadBool" --type=b && t "Bad boolean VARBadBool" + +r "--- test bad range" +tst --var=VARdm --type=i -m 0 -M -1 && t "Integer range" +tst --var=VARdm --type=u -m 1 -M 0 && t "Integer range" +tst --var=VARdm --type=r -m 0 -M -1 && t "Integer range" + +r "--- test range tested value" +tst --var=VARdm --type=i -m -1 -M 0 && t "Integer range" +tst --var=VARdm --type=i -m -50 -M -40 || f "Integer range" +tst --var=VARdm --type=u -m 0 -M -0 && t "Unsigned range" +tst --var=VARdp --type=u -m 42 -M 42 || f "Unsigned range" +tst --var=VARdm --type=u -m -50 -M -40 || f "Unsigned range" +tst --var=VARrp --type=r -m 8.0 -M 9.999 && t "Real range" +tst --var=VARrp --type=r -m 3.14 -M 45.0 || f "Real range" +tst --var=VARrm --type=r -m -1.21 -M 0.0 && t "Real range" +tst --var=VARrm --type=r -m -50.1 -M -40 || f "Real range" + +r "--- test tilde expansion without HOME" +XHOME="$HOME" +export -n HOME +( echo "[SECTION]" + echo "VAR=~/xyz" ) > xtest.ini +tst --var=VAR --sec=SECTION --tildeexpand && t "Missing HOME" + +r "--- test tilde expansion with HOME" +HOME="/we/control/home" +export HOME +tst --var=VAR --sec=SECTION --tildeexpand || f "Using HOME" +HOME="$XHOME" +export HOME +unset XHOME + +r "--- reproduce ini-file content" +( echo "[QUINE]" + echo "VAR=42" + echo "VAR=0x2a" + echo "VAR=0o52" + echo "VAR=0b101010" + echo "[NOTQUINE]" + echo "This=-42" + echo "iS=-0x2a" + echo "a=-0o52" + echo "sTrAnGe=-0b101010" + echo "Variable=Variable" ) > xtest.ini +true > quine.ini +for s in $("$INIVALUE" --sections xtest.ini); do + echo "[$s]" >> quine.ini + "$INIVALUE" --variables --content --sec "$s" xtest.ini >> quine.ini +done +if diff -u xtest.ini quine.ini >> result 2>&1; then + r "ini-file reproduction success" +else + f "quine diff" +fi + +r "--- comment removal processing" +( echo " ; comment ignored" + echo " # comment ignored" + echo "[MUSTFAILHERE" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Must fail @line 3" +( echo " [SECTION] ; comment ignored" + echo " VARa=blabla # comment ignored" + echo " VARb=blabla ; comment ignored" +) > xtest.ini +tst --var=VARa --sec=SECTION || f "Comment VARa removal" +tst --var=VARb --sec=SECTION || f "Comment VARb removal" + +r "--- string processing single/double quote" +( echo "[SECTION]" + echo "VARs='foo'" + echo "VARq=\"bar\"" ) > xtest.ini +tst --var=VARs --sec=SECTION || f "Single quote string" +tst --var=VARq --sec=SECTION || f "Double quote string" + +r "--- string merging" +( echo "[SECTION]" + echo -e "VARs='foo' \t 'bar'" + echo -e "VARq=\"bar\" \t \"foo\"" + echo -e "VARc='foo' \t \"foo\"" ) > xtest.ini +tst --var=VARs --sec=SECTION || f "Single quote merge string" +tst --var=VARq --sec=SECTION || f "Double quote merge string" +tst --var=VARc --sec=SECTION || f "Mixed quote merge string" + +r "--- string quote escapes" +( echo "[SECTION]" + echo "VARs='foo s\\' d\"'" + echo "VARq=\"bar d\\\" s'\"" ) > xtest.ini +tst --var=VARs --sec=SECTION || f "Single quote escape string" +tst --var=VARq --sec=SECTION || f "Double quote escape string" + +r "--- string bad quote escapes" +( echo "[SECTION]" + echo "VARs='foo \\\" '" # This is _not_ improper, just literal backslash followed by double quote + echo "VARq=\"bar \\' \"" ) > xtest.ini +tst --var=VARs --sec=SECTION || f "Single quote non-escape string" +tst --var=VARq --sec=SECTION || f "Double quote bad escape string" + +r "--- junk between string merge" +( echo "[SECTION]" + echo "VAR='bar' bla 'bar'" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Junk between SQ string merge" + +( echo "[SECTION]" + echo "VAR=\"bar\" bla \"bar\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Junk between DQ string merge" + +r "--- escape fail at end-of-string" +( echo "[SECTION]" + echo "VAR=\"bar \\\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "EOS escape" + +r "--- string continuations escaped newlines" +( echo "[SECTION]" + echo "VAR=\"some\n\" \\" + echo " \"string\n\" \\" + echo " \"separated on\n\" \\" + echo " \"lines\"" ) > xtest.ini +tst --var=VAR --sec=SECTION || f "String collection and continuation" + +r "--- embedded nul in values" +( echo "[SECTION]" + echo -e "VAR=val\0val" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Embedded NUL in value" +( echo "[SECTION]" + echo -e "VAR=\"val\0val\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Embedded literal NUL" +( echo "[SECTION]" + echo "VAR=\"\\0\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Embedded octal NUL" +( echo "[SECTION]" + echo "VAR=\"\\x00\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Embedded hex NUL" +( echo "[SECTION]" + echo "VAR=\"\\u0000\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Embedded UTF-16 NUL" +( echo "[SECTION]" + echo "VAR=\"\\U00000000\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Embedded UTF-32 NUL" + +r "--- escape invalid and improper hex" +( echo "[SECTION]" + echo "VAR=\"\\xYZ\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid hex escape" +( echo "[SECTION]" + echo "VAR=\"\\xY\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Improper short hex escape" +( echo "[SECTION]" + echo "VAR=\"\\xY \"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid short hex escape" +( echo "[SECTION]" + echo "VAR=\"\\uVWZY\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid hex UTF-16" +( echo "[SECTION]" + echo "VAR=\"\\uVWZ\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Improper short hex UTF-16" +( echo "[SECTION]" + echo "VAR=\"\\uVWZ \"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid short hex UTF-16" +( echo "[SECTION]" + echo "VAR=\"\\Uvwxyijkl\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid hex UTF-32" +( echo "[SECTION]" + echo "VAR=\"\\Uvwxyijk\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Improper short hex UTF-32" +( echo "[SECTION]" + echo "VAR=\"\\Uvwxyijk \"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid short hex UTF-32" + +r "--- UTF-16 surrogates, sigh" +( echo "[SECTION]" + echo "VAR=\"\\ud800\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Missing UTF-16 low surrogate" +( echo "[SECTION]" + echo "VAR=\"\\udc00\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-16 high surrogate" +( echo "[SECTION]" + echo "VAR=\"\\ud800\\ud800\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-16 low surrogate" +( echo "[SECTION]" + echo "VAR=\"\\ud800\\udc0 \"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-16 low surrogate" +( echo "[SECTION]" + # f600; smiley + echo "VAR=\"\\ud83d\\ude00\"" ) > xtest.ini +tst --var=VAR --sec=SECTION || f "Valid UTF-16" + +r "--- invalid code points" +( echo "[SECTION]" + echo "VAR=\"\\U00110000\"" ) > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid code point UTF-32" + +r "--- valid code points" +( echo "[SECTION]" + # AAAA + echo "VAR=\"\\101\\x41\\u0041\\U00000041\"" ) > xtest.ini +tst --var=VAR --sec=SECTION || f "Valid code points" + +r "--- invalid UTF-8" +echo -e '[SECTION]\nVAR="\\xc0 "' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 c0 overlap" +echo -e '[SECTION]\nVAR="\\xc1 "' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 c1 overlap" +echo -e '[SECTION]\nVAR="\\xc2"' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 c2 short seq" +echo -e '[SECTION]\nVAR="\\xe0\\x8f"' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 e08f overlap" +echo -e '[SECTION]\nVAR="\\xed\\xa0"' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 surrogate" +echo -e '[SECTION]\nVAR="\\xed\\x9f"' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 ed9f short seq" +echo -e '[SECTION]\nVAR="\\xf0\\x8f"' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f08f overlap" +echo -e '[SECTION]\nVAR="\\xf0\\x90\\x80"' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f09080 short seq" +echo -e '[SECTION]\nVAR="\\xf4\\x90"' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f490 code point" +echo -e '[SECTION]\nVAR="\\xf5 "' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f5 code point" +echo -e '[SECTION]\nVAR="\\xf6 "' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f6 code point" +echo -e '[SECTION]\nVAR="\\xf7 "' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f7 code point" +echo -e '[SECTION]\nVAR="\\xf8 "' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 f8 code point" +echo -e '[SECTION]\nVAR="\\xff "' > xtest.ini +tst --var=VAR --sec=SECTION && t "Invalid UTF-8 ff code point" +# f600; smiley: 11110 000 10 011111 10 011000 10 000000 +echo -e '[SECTION]\nVAR="\\xf0\\x9f\\x98\\x80"' > xtest.ini +tst --var=VAR --sec=SECTION || f "UTF-8 Smiley" + +r "+++ all done +++" + +rm -f xtest.ini xtest.inc quine.ini +# vim: ts=4 sw=4 diff --git a/tests/inifile/missing_values/expected b/tests/inifile/missing_values/expected index ef208405fde..1b9c79e08c8 100644 --- a/tests/inifile/missing_values/expected +++ b/tests/inifile/missing_values/expected @@ -1 +1,3 @@ -value1 +missing_section.ini:1: error: Tag 'key1' found without prior section definition + +missing_section.ini:1: error: Tag 'key1' found without prior section definition diff --git a/tests/inifile/missing_values/test.sh b/tests/inifile/missing_values/test.sh index a585d2aac56..ba0c454bb46 100755 --- a/tests/inifile/missing_values/test.sh +++ b/tests/inifile/missing_values/test.sh @@ -1,5 +1,7 @@ #!/bin/bash -inivar -ini missing_section.ini -sec section1 -var key1 -inivar -ini missing_value.ini -var key1 -inivar -ini missing_section.ini -var key1 +true > result +inivar -ini missing_section.ini -sec section1 -var key1 >> result 2>&1 +inivar -ini missing_value.ini -var key1 >> result 2>&1 +inivar -ini missing_section.ini -var key1 >> result 2>&1 +exit 0 diff --git a/tests/inifile/python_bindings/expected b/tests/inifile/python_bindings/expected index bc0832a78c9..0a74a27bff4 100644 --- a/tests/inifile/python_bindings/expected +++ b/tests/inifile/python_bindings/expected @@ -1,6 +1,109 @@ -value1 -value2 -value3 -Did not find missing value -a long value -0.000001 +Missing section was missing +Present section was present +Missing variable was missing +Present [section]variable was present +Bool 1: True +Bool 2: True +Bool 3: True +Bool 4: True +Bool 5: False +Bool 6: False +Bool 7: False +Bool 8: False +Bool 9: None +Int 1: 42 +Int 2: -42 +Int 3: 42 +Int 4: -42 +Int 5: 42 +Int 6: -42 +Int 7: 42 +Int 8: -42 +Int 9: None +Unsigned 1: 42 +Unsigned 2: 18446744073709551574 +Unsigned 3: 42 +Unsigned 4: 42 +Unsigned 5: 42 +Unsigned 6: None +Real 1: 0.1 +Real 2: 4.2e-06 +Real 3: 42.0 +Real 4: -42.0 +Real 5: None +String 1: 'String without quotes' +String 2: 'String with double quotes' +String 3: 'String with single quotes' +String 4: 'String concat with mixed quotes and so' +String 5: '' +String 6: '' +String 7: ' ' +String 8: 'Smile! πŸ˜€πŸ˜€πŸ˜€' +String 9: None +Sections: +-> section +-> booleans +-> integers +-> unsigneds +-> reals +-> strings +-> section2 +Variables of [section]: +-> ('variable', 'value') +-> ('key2', 'value 2') +-> ('key3', 'a value with continuation and nothing more') +-> ('a_long_key_that_spans_a_whole_lot_of_characters_and_is_practically_useless', 'value4') +-> ('number', '0.000001') +Variables of all sections: +-> ('variable', 'value') +-> ('key2', 'value 2') +-> ('key3', 'a value with continuation and nothing more') +-> ('a_long_key_that_spans_a_whole_lot_of_characters_and_is_practically_useless', 'value4') +-> ('number', '0.000001') +-> ('bool', 'true') +-> ('bool', 'yes') +-> ('bool', '1') +-> ('bool', 'on') +-> ('bool', 'false') +-> ('bool', 'no') +-> ('bool', '0') +-> ('bool', 'off') +-> ('bool', 'invalid') +-> ('int', '42') +-> ('int', '-42') +-> ('int', '0x2a') +-> ('int', '-0x2a') +-> ('int', '0o52') +-> ('int', '-0o52') +-> ('int', '0b101010') +-> ('int', '-0b101010') +-> ('int', 'invalid') +-> ('unsigned', '42') +-> ('unsigned', '-42') +-> ('unsigned', '0x2a') +-> ('unsigned', '0o52') +-> ('unsigned', '0b101010') +-> ('unsigned', 'invalid') +-> ('real', '0.1') +-> ('real', '0.0000042') +-> ('real', '42') +-> ('real', '-4.2e1') +-> ('real', 'invalid') +-> ('string', 'String without quotes') +-> ('string', 'String with double quotes') +-> ('string', 'String with single quotes') +-> ('string', 'String concat with mixed quotes and so') +-> ('string', '') +-> ('string', '') +-> ('string', ' ') +-> ('string', 'Smile! πŸ˜€πŸ˜€πŸ˜€') +-> ('key1', 'value1') +-> ('key1', 'value2') +-> ('key1', 'value3') +-> ('key4', 'value4') +Variables of non-existent section: [] +values.ini:22: [integers]int='42' +values.ini:50: [strings]string='String with single quotes' +None:None: [missing]missing=None +findall [section2]key1: value1, value2, value3 +findall [section2]: value1, value2, value3, value4 diff --git a/tests/inifile/python_bindings/test b/tests/inifile/python_bindings/test index fe859dd97a3..dd549bdc8d4 100755 --- a/tests/inifile/python_bindings/test +++ b/tests/inifile/python_bindings/test @@ -1,17 +1,101 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 import linuxcnc -inifile = linuxcnc.ini("values.ini") +ini = linuxcnc.ini("values.ini") -missing_value = inifile.find("section1", "missing") -value_with_space = inifile.find("section1", "key3") -numbered_value = inifile.find("section1", "number") -list_of_values = inifile.findall("section2", "key1") +# Test availability testing +if ini.hassection("notasection"): + print("Missing section was wrongfully detected") +else: + print("Missing section was missing") -for value in list_of_values: - print(value) -if missing_value is None: - print("Did not find missing value") -print(value_with_space) -print(numbered_value) +if ini.hassection("section"): + print("Present section was present") +else: + print("Present section was wrongfully detected as not present") + +if ini.hasvariable("section", "missing"): + print("Missing variable was detected to be present") +else: + print("Missing variable was missing") + +if ini.hasvariable("section", "variable"): + print("Present [section]variable was present") +else: + print("Present [section]variable was wrongfully detected as not present") + +# Test booleans +for n in range(1, 9+1): + print("Bool {}: {}".format(n, ini.getbool("booleans", "bool", n))) +if True != ini.getbool("booleans", "missing", fallback=True): + print("Bool default True failed") +if False != ini.getbool("booleans", "missing", fallback=False): + print("Bool default False failed") +if "hello" != ini.getbool("booleans", "missing", fallback="hello"): + print("Bool default 'hello' failed") + +# Test integers +for n in range(1, 9+1): + print("Int {}: {}".format(n, ini.getsint("integers", "int", n))) +if 42 != ini.getsint("integers", "missing", fallback=42): + print("Int default 42 failed") +if "_42" != ini.getsint("integers", "missing", fallback="_42"): # any fallback type is returned + print("Int default '_42' failed") +if ini.getsint("integers", "int") != ini.getint("integers", "int"): + print("getsint/getint alias error") + +# Test unsigneds +for n in range(1, 6+1): + print("Unsigned {}: {}".format(n, ini.getuint("unsigneds", "unsigned", n))) +if 42 != ini.getuint("unsigneds", "missing", fallback=42): + print("Unsigned default 42 failed") +if "_42" != ini.getuint("unsigneds", "missing", fallback="_42"): # any fallback type is returned + print("Unsigned default '_42' failed") + +# Test reals +for n in range(1, 5+1): + print("Real {}: {}".format(n, ini.getreal("reals", "real", n))) +if 42 != ini.getreal("reals", "missing", fallback=4.2e1): + print("Real default 4.2e1 failed") +if "_42" != ini.getreal("reals", "missing", fallback="_42"): # any fallback type is returned + print("Real default '_42' failed") +if ini.getreal("reals", "real") != ini.getfloat("reals", "real"): + print("getreal/getfloat alias error") + +# Test strings +for n in range(1, 8+1): + print("String {}: '{}'".format(n, ini.getstring("strings", "string", n))) +# One more to get None back, but we don't want the quotes +print("String {}: {}".format(n+1, ini.getstring("strings", "string", n+1))) +if None != ini.getstring("strings", "missing"): + print("String not found failed") +if "|_-42-_|" != ini.getstring("strings", "missing", fallback="|_-42-_|"): + print("String default '|_-42-_|' failed") + +# Test sections and variables +print("Sections:") +for s in ini.getsections(): + print("-> {}".format(s)) +print("Variables of [section]:") +for v in ini.getvariables("section"): + print("-> {}".format(v)) +print("Variables of all sections:") +for v in ini.getvariables(): + print("-> {}".format(v)) +print("Variables of non-existent section:", ini.getvariables("not_a_section_we_know_of")) + +# Test find/findall +if None != ini.find("section", "missing"): + print("find: Missing value was not missing") + +# Test lineof +f, l = ini.lineof("integers", "int") +print("{}:{}: [integers]int='{}'".format(f, l, ini.getint("integers", "int"))) +f, l = ini.lineof("strings", "string", 3) +print("{}:{}: [strings]string='{}'".format(f, l, ini.getstring("strings", "string", 3))) +f, l = ini.lineof("missing", "missing") +print("{}:{}: [missing]missing={}".format(f, l, ini.getstring("missing", "missing"))) + +print("findall [section2]key1:", ", ".join(ini.findall("section2", "key1"))) +print("findall [section2]:", ", ".join(ini.findall("section2", ""))) diff --git a/tests/inifile/python_bindings/values.ini b/tests/inifile/python_bindings/values.ini index f12734e5b1f..8d5d4b68928 100644 --- a/tests/inifile/python_bindings/values.ini +++ b/tests/inifile/python_bindings/values.ini @@ -1,10 +1,59 @@ -[section1] -key1=value1 +[section] +variable=value key2=value 2 -key3 = a long value -a long key = value4 +key3 = a value \ +with continuation \ +and nothing more +a_long_key_that_spans_a_whole_lot_of_characters_and_is_practically_useless = value4 number = 0.000001 +[booleans] +bool = true +bool = yes +bool = 1 +bool = on +bool = false +bool = no +bool = 0 +bool = off +bool = invalid + +[integers] +int = 42 +int = -42 +int = 0x2a +int = -0x2a +int = 0o52 +int = -0o52 +int = 0b101010 +int = -0b101010 +int = invalid + +[unsigneds] +unsigned = 42 +unsigned = -42 +unsigned = 0x2a +unsigned = 0o52 +unsigned = 0b101010 +unsigned = invalid + +[reals] +real = 0.1 +real = 0.0000042 +real = 42 +real = -4.2e1 +real = invalid + +[strings] +string = String without quotes +string = "String with double quotes" +string = 'String with single quotes' +string = "String concat " "with" ' mixed ' 'quotes' " and so" +string = # empty +string = "" # empty too +string = " " # a space, don't know why you'd want that... +string = "Smile! \U0001F600\ud83d\ude00\xf0\x9f\x98\x80" + [section2] key1=value1 key1=value2 diff --git a/tests/inifile/values/test.sh b/tests/inifile/values/test.sh index f2588a83164..f86bd62307d 100755 --- a/tests/inifile/values/test.sh +++ b/tests/inifile/values/test.sh @@ -3,5 +3,5 @@ inivar -ini values.ini -var key1 inivar -ini values.ini -var key2 inivar -ini values.ini -var key3 -inivar -ini values.ini -var "a long key" +inivar -ini values.ini -var "alongkey" inivar -ini values.ini -var number diff --git a/tests/inifile/values/values.ini b/tests/inifile/values/values.ini index d9d17f4b9c1..3110bb01a35 100644 --- a/tests/inifile/values/values.ini +++ b/tests/inifile/values/values.ini @@ -2,5 +2,5 @@ key1=value1 key2=value 2 key3 = a long value -a long key = value4 +alongkey = value4 number = 0.000001 diff --git a/tests/remap/fail/args.2/test.ini b/tests/remap/fail/args.2/test.ini index ecd049d5ee1..094a2623fb5 100644 --- a/tests/remap/fail/args.2/test.ini +++ b/tests/remap/fail/args.2/test.ini @@ -7,4 +7,3 @@ SUBROUTINE_PATH = . # M405 requires P- Q- and does NOT permit any other words in this block REMAP=M400 modalgroup=5 argspec=PQ ngc=rm400 --