# scpi-parser: Introduction of `HELP?` ## Links Issue: https://github.com/j123b567/scpi-parser/issues/129 PR: https://github.com/j123b567/scpi-parser/pull/135 ## Motivation First standadized three dacades ago in 1992, the Standard Commands for Programmable Instruments (SCPI) have been, and continue to be, a common way to communicate with lab instruments of all sorts. With the exception of less-frequently used block data, SCPI commands and reponses are human-readable both in terms of mnemonics and parameters. Instruments supporting SCPI usually come with detailed `Programming Guide` documents that spell out and explain commands and parameter types, parameter ranges and the corresponding return values. In essence, there is a long-standing tradition how to implement and document SCPI, which has historically been the domain of big corporations with the sophistication of technical documentation a hallmark of their overall dedication to quality. Through the rise of open source hardware and a broadening offering of lower-cost lab equipment (e.g. [EastTester](https://budgetlightforum.com/node/73303)), things have changed a bit. Some problems can therefore arise through current and future use of SCPI: - SCPI Programming Guides for older devices, especially exotic lab equipment, can be hard to find or only found in print form. Manufacturers that had gone out of business may be unreachable. - Open source project documentation may often be non-existent or out-of-date. - Higher release cadence increases the chance for documentation to become incongruent with the feature set. - Documentation may not be properly archived and become hard to find / lost when an open source project is updated. - Archiving of documentation for hardware that cannot be updated in a running setup more often than not becomes a private matter. - Implementation of instrument control libraries and debugging still relies on the use of the Programming Guide (or source code). - SCPI as per its old standard may feel complex and tedious to new users and hinder continued adption, as most of what makes implementing and debugging systems with SCPI efficient is the memorization or access to command documentation and test cases. A cursory glance at [scpi-parser forks](https://techgaun.github.io/active-forks/index.html#j123b567/scpi-parser) and repositories by the same people, as well as some online search hints at the reality of the use of this library: - [scpi_sx127x](https://os.mbed.com/teams/Semtech/code/scpi_sx127x/file/8767be3c1b7f/scpi-def.cpp/) essentially takes the example code and tacks on the application-specific commands. The documentation is succinct and would immediately translate to description strings as described below. - [RedPitaya](https://github.com/RedPitaya/RedPitaya/blob/master/scpi-server/src/scpi-commands.c) is probably one of the scpi-parser use cases with the longest list of commands and an actual separate, complete [command list](https://github.com/RedPitaya/RedPitaya/tree/master/scpi-server/doc). - In [mini project: PICO-PI programmable Lab Switch - 2: add SCPI parser](https://community.element14.com/technologies/test-and-measurement/b/blog/posts/mini-project-pico-pi-programmable-lab-switch---2-add-scpi-parser-669206140), one can see best the expectations towards an open source scpi parser library and "starter pack" with a set of usable examples: **"The complexity of what you and I have to do is low. We have to integrate an existing SCPI parser. But the yield is high: you get the (operational!) skeleton of a programmable lab device."**. It is however also clear that the library and implementation that interfaces with it is stricly a means to an end, and it's hard to see someone take the time to produce a legitimate interface documentation. - ... Good documentation is a task in its own right, and not everyone has the love to spare for it. One could argue that the quality of the outcomes of lower-effort open source projects is a function of the examples and paradigms provided to get people started. Along with better code examples, there is thus an opportunity to add a low-threshold means to add documentation. In a [Hackaday article](https://hackaday.com/2021/11/17/scpi-on-teaching-your-devices-the-lingua-franca-of-laboratories/) on SCPI, it also became evident that some programmers end up re-inventing the wheel or seek to implement an "easier" (albeit non-compliant) version of SCPI or something radically different altogether. Apart from promoting `scpi-parser`, it won't hurt to make the experience, or at least the outcomes of using the library more user-friendly. ## Proposed Enhancement Modern microcontrollers, even lower-end ones, tend to offer enough memory to store a set of strings to describe commands and data types on top of the command patterns which are already in place for command parsing. It is therefore possible to: - introduce a `HELP?` command to query all supported commands, - pass a search string via `HELP? "<string>"` to narrow down the search, and - append command descriptions to `_scpi_command_t`. Return type: - A comma-separated list of arbitrary block data. - Block data format is: `#1A1234...xyz`, `#2AB1234...xyz` or `#3ABC1234...xyz` where the first digit specifies the number of data length (in Bytes) digits A, AB or ABC. - Line break characters are absorbed into the data block. This provides conformity and human readability in a console. PC software processing the block data should trim these extra characters. The mnemonic `HELP?` is chosen as the SCPI version of `--help` (see Gnu Coding Standards, [4.8 Standards for Command Line Interfaces](https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html#Command_002dLine-Interfaces)). In scpi-99 and later, `IEEE-488.2 Common Commands` are decorated with a leading asterisk. When reading this as a way to identify Common Command as required by the standard, it is probably not recommended (although not explicitly discouraged) to mimic them in the form of e.g. `*HLP?`. Application code still retains full control over the extent to which these features are included, up to removal of the new `HELP?` handler function `SCPI_HelpQ` when found to be unused during optimization. On the PC side, applications can parse returned block data to display commands in a drop-down list, or to show suggestions as the user types commends to directly interact with the SCPI device. ## Example Shown below are the two types of queries with the full set of features in place: - `HELP?` returning the full list of supported commands highlighting optional and mandatory parts, as well as plain text descriptions of each function. Parameters can be explained on a per-command basis. - `HELP? "*"` returning a reduced list of entries matching the search string `*` (case insensitive). ![raw comms example]( data:image/png;base64,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) **Note:** When compiled with `USE_COMMAND_DESCRIPTIONS` disabled, the user is presented the list of supported commands which are already stored as strings. One would still need to know (or guess) the number and type of input values. At the small overhead this bare-bones implementation comes (especially with `USE_HELP_FILTER` disabled and `strncasestrn` optimized away), it's hard to find reasons other than aesthetics / needless minimalism against the addition of a `HELP?` command. ## Limitations - Currently, only command strings are being searched when a string is passed after `HELP?`. If needed, the boolean expression in `SCPI_HelpQ(scpi_t * context)` can be expanded accordingly after checking `USE_COMMAND_DESCRIPTIONS` (see below). - It is not recommended to add a `HELP` command without `?`, as this would not comply with standard requirements. The user is expected to have this degree of familiarity with SCPI to know that queries are built with a question mark. ## Code Contribution ### Options The size and structure of `_scpi_command_t` entries is parameterized through `USE_COMMAND_TAGS` and `USE_COMMAND_DESCRIPTIONS` options. In `inc/scpi/types.h`: ```cpp struct _scpi_command_t { const char * pattern; scpi_command_callback_t callback; #if USE_COMMAND_DESCRIPTIONS const char * description; #endif #if USE_COMMAND_TAGS int32_t tag; #endif /* USE_COMMAND_TAGS */ }; /* Helper macros for _scpi_command_t items. Usage: _scpi_command_t cmd = { .pattern = ":SOME:PATTern" , .callback = SCPI_StubQ , SCPI_CMD_DESC("\t - a command") SCPI_CMD_TAG(0) }; */ #if USE_COMMAND_DESCRIPTIONS #define SCPI_CMD_DESC(S) (S), #else #define SCPI_CMD_DESC(S) #endif #if USE_COMMAND_TAGS #define SCPI_CMD_TAG(T) (T), #else #define SCPI_CMD_TAG(T) #endif ``` Their defaults are given below. In `inc/scpi/config.h`: ```cpp #ifndef USE_COMMAND_TAGS #define USE_COMMAND_TAGS 1 #endif #ifndef USE_COMMAND_DESCRIPTIONS #define USE_COMMAND_DESCRIPTIONS 0 #endif //... #ifndef USE_HELP_FILTER #define USE_HELP_FILTER 1 #endif ``` To override defaults, create and include `scpi_user_config.h` containing the corresponding definitions. When using cmake, als add ```cmake add_definitions(-DSCPI_USER_CONFIG=1) ``` to `CMakeLists.txt`. ### SCPI\_HelpQ Default Handler In `libscpi/inc/scpi/minimal.h`: ```cpp scpi_result_t SCPI_HelpQ(scpi_t * context); ``` In `libscpi/src/minimal.c`: With `USE_HELP_FILTER 0` and `USE_COMMAND_DESCRIPTIONS 0`, the `HELP?` command would reduce to the function below (given for illustration purposes only): ```cpp scpi_result_t SCPI_HelpQ(scpi_t * context) { for(int i = 0; context->cmdlist[i].pattern != NULL; i++) { size_t pattern_len = strlen(context->cmdlist[i].pattern); size_t block_len = 1 + pattern_len + strlen(SCPI_LINE_ENDING); SCPI_ResultArbitraryBlockHeader(context, block_len); SCPI_ResultArbitraryBlockData(context, "\t", 1); SCPI_ResultArbitraryBlockData(context, context->cmdlist[i].pattern, pattern_len); SCPI_ResultArbitraryBlockData(context, SCPI_LINE_ENDING, strlen(SCPI_LINE_ENDING)); } return SCPI_RES_OK; } ``` `SCPI_ResultArbitraryBlockDataHeader` produces a header of numbers preceded by '#' and variable length, so to achieve proper horizontal alignment, a tab character is used as the first data byte, followed by the i-th pattern string, a `SCPI_LINE_ENDING`sequence and a comma. The comma ends up leading the next line, separating consecutive arbitrary data blocks. Neither the comma nor the header characters are included in `block_len`. A search string passed as `HELP? "<string>""` is optional (`narrowed_down` is `True` if given, and only patterns containing the search string are returned). The full `SCPI_HelpQ` handler also outputs description strings: ```cpp /** * HELP? [<string>] * @param context * @return */ scpi_result_t SCPI_HelpQ(scpi_t * context) { #if USE_HELP_FILTER size_t search_string_len = 0; const char * search_string = NULL; scpi_bool_t narrowed_down = SCPI_ParamCharacters( context, &search_string, &search_string_len, false); #endif for(int i = 0; context->cmdlist[i].pattern != NULL; i++) { size_t pattern_len = strlen(context->cmdlist[i].pattern); #if USE_HELP_FILTER if(narrowed_down && (NULL == strncasestrn(context->cmdlist[i].pattern, pattern_len, search_string, search_string_len))){ continue; } #endif size_t block_len = 1 + pattern_len + strlen(SCPI_LINE_ENDING); #if USE_COMMAND_DESCRIPTIONS size_t description_len = context->cmdlist[i].description ? strlen(context->cmdlist[i].description) : 0; if(description_len > 0){ block_len = 1 + pattern_len + 1 + description_len + strlen(SCPI_LINE_ENDING); } #endif SCPI_ResultArbitraryBlockHeader(context, block_len); SCPI_ResultArbitraryBlockData(context, "\t", 1); SCPI_ResultArbitraryBlockData(context, context->cmdlist[i].pattern, pattern_len); #if USE_COMMAND_DESCRIPTIONS if(description_len > 0){ SCPI_ResultArbitraryBlockData(context, " ", 1); SCPI_ResultArbitraryBlockData(context, context->cmdlist[i].description, description_len); } #endif SCPI_ResultArbitraryBlockData(context, SCPI_LINE_ENDING, strlen(SCPI_LINE_ENDING)); } return SCPI_RES_OK; } ``` ### Utils A case-insensitive `strnstrn` implementation is not part of libc or newlib and must be provided separately (seek first character match, then invoke `strncasecmp`): In `libscpi/src/utils_private.h`: ```cpp char * strncasestr (const char *s, const char *find, size_t slen); char * strncasestrn (const char *s, size_t slen, const char *find, size_t findlen); #ifndef strncasestrn_s #define strncasestrn_s(s,s_len,lit) strncasestrn(s, s_len, lit, sizeof(lit)-1) #endif ``` In `libscpi/src/utils.c`: ```cpp #define CHAR_TO_UPPER(c) ((c)>96 && (c)<123 ? (char)((c) ^ 0x20) : (c)) #define CHAR_TO_LOWER(c) ((c)>64 && (c)< 91 ? (char)((c) | 0x20) : (c)) /** * @brief Locate a binary substring within a binary string (case-insensitive `strnstrn`). * @param[in] s binary string * @param[in] slen length of binary string s * @param[in] find binary substring * @param[in] findlen length of binary substring find * @return Pointer to first match in s if found, otherwise `NULL`. * @author Alvaro Lopez Ortega <alvaro@alobbs.com> */ char * strncasestrn (const char *s, size_t slen, const char *find, size_t findlen) { char first; char cursor_chr; if ((find == NULL) || (findlen == 0)) return (char *)s; if ((*find == '\0')) return (char *)s; first = CHAR_TO_LOWER(*find); find++; findlen--; do { do { if (slen-- < 1 || (cursor_chr = *s++) == '\0') return NULL; } while (CHAR_TO_LOWER(cursor_chr) != first); if (findlen > slen) { return NULL; } } while (strncasecmp (s, find, findlen) != 0); s--; return (char *)s; } /** * @brief Locate a substring in a binary string (case-insensitive `strnstr`). * @param[in] s binary string * @param[in] find substring (zero-terminated) * @param[in] slen length of binary string s * @return Pointer to first match in s if found, otherwise `NULL`. */ char * strncasestr (const char *s, const char *find, size_t slen) { return strncasestrn (s, slen, find, strlen(find)); } ``` (Adaptation the implementation found in utils.c of the [Cherokee Web Server](https://github.com/cherokee/webserver/blob/5b1dbdb4dd68872014874ac05f8af0f833343d0f/cherokee/util.c) project.) ### Adding SCPI `HELP?` To Your Project A `HELP?` command pattern should be the first item in the command list. General use of `SCPI_CMD_DESC?` is recommended even when `USE_COMMAND_DESCRIPTIONS` excludes the strings from the data structure, as it adds a level of documentation and functionality written at a time of best understanding of the intent regarding the corresponding handler. Later on, `SCPI_CMD_DESC` can still become effective when code is re-used or when the project is ported to a device with sufficient memory. ```cpp const scpi_command_t scpi_commands[] = { /* Optional help command */ #if USE_HELP_FILTER {"HELP?", SCPI_HelpQ, SCPI_CMD_DESC("[<string>] - list supported commands [containing \"<string>\"] (multiple block data)") SCPI_CMD_TAG(0)}, #else {"HELP?", SCPI_HelpQ, SCPI_CMD_DESC("\t - list supported commands (multiple block data)") SCPI_CMD_TAG(0)}, #endif {"*CLS", SCPI_CoreCls, SCPI_CMD_DESC("\t - clear all Event Status registers, errors, output queue") SCPI_CMD_TAG(0)}, {"*ESE", SCPI_CoreEse, SCPI_CMD_DESC("<0..255> - set Standard Event Status Enable / event mask") SCPI_CMD_TAG(0)}, {"*ESE?", SCPI_CoreEseQ, SCPI_CMD_DESC("\t - read ESE (0..255)") SCPI_CMD_TAG(0)}, {"*ESR?", SCPI_CoreEsrQ, SCPI_CMD_DESC("\t - read+clear Standard Event Status register (0..255)") SCPI_CMD_TAG(0)}, ``` Exemplary descriptions are included in the `Examples` common definitions. When no parameters are expected, it could look like: ```cpp "\t - description (return value or range)" ``` Note that in `SCPI_HelpQ`, a space character is inserted after the pattern if a description string is available, so when parameters are expected, mandatory and optional parameters can be listed immediately: ```cpp "<parameter>[,<optional param>] - description (return value or range)" ``` No space character is needed, as it will automatically be added between the command pattern and command description strings. Arbitrary block data is fully specified through the header and not searched for special characters, line endings or other. Therefore, no particular limitations to the nature of command description strings exist.