editable serial input line buffer

Dependents:   MAX5715BOB_Tester MAX11131BOB_Tester MAX5171BOB_Tester MAX11410BOB_Tester ... more

CmdLine.cpp

Committer:
whismanoid
Date:
2019-12-19
Revision:
12:447a747099e6
Parent:
11:e8a4162d4fd1
Child:
13:abedfe18f924

File content as of revision 12:447a747099e6:

// /*******************************************************************************
// * Copyright (C) 2019 Maxim Integrated Products, Inc., All Rights Reserved.
// *
// * Permission is hereby granted, free of charge, to any person obtaining a
// * copy of this software and associated documentation files (the "Software"),
// * to deal in the Software without restriction, including without limitation
// * the rights to use, copy, modify, merge, publish, distribute, sublicense,
// * and/or sell copies of the Software, and to permit persons to whom the
// * Software is furnished to do so, subject to the following conditions:
// *
// * The above copyright notice and this permission notice shall be included
// * in all copies or substantial portions of the Software.
// *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
// * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// * OTHER DEALINGS IN THE SOFTWARE.
// *
// * Except as contained in this notice, the name of Maxim Integrated
// * Products, Inc. shall not be used except as stated in the Maxim Integrated
// * Products, Inc. Branding Policy.
// *
// * The mere transfer of this software does not imply any licenses
// * of trade secrets, proprietary technology, copyrights, patents,
// * trademarks, maskwork rights, or any other form of intellectual
// * property whatsoever. Maxim Integrated Products, Inc. retains all
// * ownership rights.
// *******************************************************************************
// */
// *********************************************************************
// @file CmdLine.cpp
// *********************************************************************

#include "CmdLine.h"
// toupper requires include <ctype.h>
#include <ctype.h>

CmdLine::CmdLine(Stream& AssociatedSerialPort, const char *Name)
    : associatedSerialPort(AssociatedSerialPort)
    , name(Name)
    , onEOLcommandParser()
{
    indexOfNextEmptyCell = 0;
    memset(buf, 0, COMMAND_BUFFER_LENGTH);
    chSeparator = 0;
    match_is_case_sensitive = false;
}
/** CmdLine::clear empties the command-line buffer */
void CmdLine::clear(void)
{
    indexOfNextEmptyCell = 0;
    memset(buf, 0, COMMAND_BUFFER_LENGTH);
}
//void CmdLine::idleAppendIfReadable()
//{
//    // append ch to buf, unless BS or EOL or other editing character
//    // Polymorphism fail: associatedSerialPort.readable()
//    if (associatedSerialPort.readable()) {
//        append(associatedSerialPort.getc());
//        //
//        // TODO1: set EOL timeout, so that we don't get lingering buffer cruft
//        //
//    }
//}
/** CmdLine::append handles an input character by appending the buffer,
 * or handling an immediate function like backspace/delete
 * or other custom immediate motor control functions.
 */
void CmdLine::append(char ch)
{
    // void diagnostic_led_EOF();
    void main_menu_status(CmdLine & cmdLine);

    // append ch to buf, unless BS or EOL or other editing character
    switch (ch)
    {
        case '\b': // Unicode (U+0008) BS BACKSPACE as destructive backspace
        case '\x7f': // Unicode (U+007F) DEL DELETE as destructive backspace
            if (indexOfNextEmptyCell > 0)
            {
                buf[--indexOfNextEmptyCell] = '\0'; // pre-decrement index, overwrite with null
                associatedSerialPort.printf("\b \b"); // tty: backspace, overwrite with space, backspace
            }
            break;
        case '\r': // Unicode (U+000D) CR CARRIAGE RETURN(CR) as EOL end of line
        case '\n': // Unicode (U+000A) LF LINE FEED(LF) as EOL end of line
            //associatedSerialPort.printf("%c", ch); // echo line end
            associatedSerialPort.printf("\r\n"); // echo line end
            //~ associatedSerialPort.printf("\r\n~%s~\r\n", buf); // DIAGNOSTIC: print line buffer
            // parse and handle the command by invoking onEOLcommandParser callback
            if (onEOLcommandParser) {
                onEOLcommandParser(*this);
            }
            clear();
            break;
#define ECHO_EOF_IMMEDIATELY 1
#if ECHO_EOF_IMMEDIATELY
        case '\x04': // Unicode (U+0004) EOT END OF TRANSMISSION = CTRL+D as EOF end of file
            if (diagnostic_led_EOF) { diagnostic_led_EOF(); }
            //~ main_menu_status(*this);
            associatedSerialPort.printf("** U+0004 EOT = EOF **"); // immediately echo EOF for test scripting
            if (diagnostic_led_EOF) { diagnostic_led_EOF(); }
            associatedSerialPort.printf("\r\n\x04\r\n"); // immediately echo EOF for test scripting
            //~ associatedSerialPort.printf("\x1a"); // immediately echo EOF for test scripting
            //~ associatedSerialPort.printf("\x04"); // immediately echo EOF for test scripting
            //~ associatedSerialPort.printf("\x1a"); // immediately echo EOF for test scripting
            clear(); // EOF discard any pending commands, to avoid surprise
            break;
        case '\x1a': // Unicode (U+001A) SUB SUBSTITUTE = CTRL+Z as EOF end of file
            if (diagnostic_led_EOF) { diagnostic_led_EOF(); }
            //~ main_menu_status(*this);
            associatedSerialPort.printf("** U+001A SUB = EOF **"); // immediately echo EOF for test scripting
            if (diagnostic_led_EOF) { diagnostic_led_EOF(); }
            associatedSerialPort.printf("\x1a"); // immediately echo EOF for test scripting
            associatedSerialPort.printf("\x04"); // immediately echo EOF for test scripting
            associatedSerialPort.printf("\x1a"); // immediately echo EOF for test scripting
            associatedSerialPort.printf("\x04"); // immediately echo EOF for test scripting
            clear(); // EOF discard any pending commands, to avoid surprise
            break;
#endif
        //
        // Support commands that get handled immediately w/o waiting for EOL
        // Avoid using characters that may appear in other commands,
        // such as 0-9 A-Z a-z and some punctuation %*+-./=
        // so these 25 characters are available: !"#$&'(),:;<>?@[\]^_`{|}~
        //case '!': // immediate command !) example
        //    do_some_immediate_action();
        //    clear();
        //    break;
        //case ' ':
        //    on_immediate_0x20(); // Unicode (U+0020)   SPACE
        //    break;
        case '!':
            //~ on_immediate_0x21(); // Unicode (U+0021) ! EXCLAMATION MARK
            //~ break; // handled as immediate command, do not append to buffer
            if (on_immediate_0x21) {
                on_immediate_0x21(); // Unicode (U+0021) ! EXCLAMATION MARK
                break; // handled as immediate command, do not append to buffer
            }
        case '{':
            //~ on_immediate_0x7b(); // Unicode (U+007B) { LEFT CURLY BRACKET
            //~ break; // handled as immediate command, do not append to buffer
            if (on_immediate_0x7b) {
                on_immediate_0x7b(); // Unicode (U+007B) { LEFT CURLY BRACKET
                break; // handled as immediate command, do not append to buffer
            }
        case '}':
            //~ on_immediate_0x7d(); // Unicode (U+007D) } RIGHT CURLY BRACKET
            //~ break; // handled as immediate command, do not append to buffer
            if (on_immediate_0x7d) {
                on_immediate_0x7d(); // Unicode (U+007D) } RIGHT CURLY BRACKET
                break; // handled as immediate command, do not append to buffer
            }
        //
        // default:
        case '"':
            if (on_immediate_0x22) {
                on_immediate_0x22(); // Unicode (U+0022) " QUOTATION MARK
                break; // handled as immediate command, do not append to buffer
            }
        case '#':
            if (on_immediate_0x23) {
                on_immediate_0x23(); // Unicode (U+0023) # NUMBER SIGN = pound sign, hash, crosshatch
                break; // handled as immediate command, do not append to buffer
            }
        case '$':
            if (on_immediate_0x24) {
                on_immediate_0x24(); // Unicode (U+0024) $ DOLLAR SIGN
                break; // handled as immediate command, do not append to buffer
            }
        //~ on_immediate_0x24(); // Unicode (U+0024) $ DOLLAR SIGN
        //~ break; // handled as immediate command, do not append to buffer
        //case '%':
        //    on_immediate_0x25(); // Unicode (U+0025) % PERCENT SIGN
        //    break;
        case '&':
            if (on_immediate_0x26) {
                on_immediate_0x26(); // Unicode (U+0026) & AMPERSAND
                break; // handled as immediate command, do not append to buffer
            }
        case '\'':
            if (on_immediate_0x27) {
                on_immediate_0x27(); // Unicode (U+0027) ' APOSTROPHE
                break; // handled as immediate command, do not append to buffer
            }
        case '(':
            if (on_immediate_0x28) {
                on_immediate_0x28(); // Unicode (U+0028) ( LEFT PARENTHESIS
                break; // handled as immediate command, do not append to buffer
            }
        case ')':
            if (on_immediate_0x29) {
                on_immediate_0x29(); // Unicode (U+0029) ) RIGHT PARENTHESIS
                break; // handled as immediate command, do not append to buffer
            }
        //case '*':
        //    on_immediate_0x2a(); // Unicode (U+002A) * ASTERISK
        //    break;
        //case '+':
        //    on_immediate_0x2b(); // Unicode (U+002B) + PLUS SIGN
        //    break;
        case ',':
            if (on_immediate_0x2c) {
                on_immediate_0x2c(); // Unicode (U+002C) , COMMA
                break; // handled as immediate command, do not append to buffer
            }
        //case '-':
        //    on_immediate_0x2d(); // Unicode (U+002D) - HYPHEN-MINUS
        //    break;
        //case '.':
        //    on_immediate_0x2e(); // Unicode (U+002E) . FULL STOP
        //    break;
        //case '/':
        //    on_immediate_0x2f(); // Unicode (U+002F) / SOLIDUS =SLASH
        //    break;
        case ':':
            if (on_immediate_0x3a) {
                on_immediate_0x3a(); // Unicode (U+003A) : COLON
                break; // handled as immediate command, do not append to buffer
            }
        case ';':
            if (on_immediate_0x3b) {
                on_immediate_0x3b(); // Unicode (U+003B) ; SEMICOLON
                break; // handled as immediate command, do not append to buffer
            }
        case '<':
            if (on_immediate_0x3c) {
                on_immediate_0x3c(); // Unicode (U+003C) < LESS-THAN SIGN
                break; // handled as immediate command, do not append to buffer
            }
        //case '=':
        //    on_immediate_0x3d(); // Unicode (U+003D) = EQUALS SIGN
        //    break;
        case '>':
            if (on_immediate_0x3e) {
                on_immediate_0x3e(); // Unicode (U+003E) > GREATER-THAN SIGN
                break; // handled as immediate command, do not append to buffer
            }
        case '?':
            if (on_immediate_0x3f) {
                on_immediate_0x3f(); // Unicode (U+003F) ? QUESTION MARK
                break; // handled as immediate command, do not append to buffer
            }
        case '@':
            if (on_immediate_0x40) {
                on_immediate_0x40(); // Unicode (U+0040) @ COMMERCIAL AT = at sign
                break; // handled as immediate command, do not append to buffer
            }
        case '[':
            if (on_immediate_0x5b) {
                on_immediate_0x5b(); // Unicode (U+005B) [ LEFT SQUARE BRACKET
                break; // handled as immediate command, do not append to buffer
            }
        case '\\':
            if (on_immediate_0x5c) {
                on_immediate_0x5c(); // Unicode (U+005C) \ REVERSE SOLIDUS
                break; // handled as immediate command, do not append to buffer
            }
        case ']':
            if (on_immediate_0x5d) {
                on_immediate_0x5d(); // Unicode (U+005D) ] RIGHT SQUARE BRACKET
                break; // handled as immediate command, do not append to buffer
            }
        case '^':
            if (on_immediate_0x5e) {
                on_immediate_0x5e(); // Unicode (U+005E) ^ CIRCUMFLEX ACCENT
                break; // handled as immediate command, do not append to buffer
            }
        case '_':
            if (on_immediate_0x5f) {
                on_immediate_0x5f(); // Unicode (U+005F) _ LOW LINE =SPACING UNDERSCORE
                break; // handled as immediate command, do not append to buffer
            }
        case '`':
            if (on_immediate_0x60) {
                on_immediate_0x60(); // Unicode (U+0060) ` GRAVE ACCENT (also called backtick)
                break; // handled as immediate command, do not append to buffer
            }
        case '|':
            if (on_immediate_0x7c) {
                on_immediate_0x7c(); // Unicode (U+007C) | VERTICAL LINE
                break; // handled as immediate command, do not append to buffer
            }
        case '~':
            if (on_immediate_0x7e) {
                on_immediate_0x7e(); // Unicode (U+007E) ~ TILDE
                break; // handled as immediate command, do not append to buffer
            }
        //
        default:
            MBED_ASSERT(indexOfNextEmptyCell <= COMMAND_BUFFER_LENGTH - 2);
            buf[indexOfNextEmptyCell++] = ch; // append character, post-increment index
            buf[indexOfNextEmptyCell] = '\0'; // null-terminate the buffer
            MBED_ASSERT(indexOfNextEmptyCell <= COMMAND_BUFFER_LENGTH - 1);
            associatedSerialPort.printf("%c", ch); // echo
            if (indexOfNextEmptyCell == COMMAND_BUFFER_LENGTH - 1)
            {
                // buffer is full, parse what we've got
                if (onEOLcommandParser) {
                    onEOLcommandParser(*this);
                }
                clear();
            }
            break;
    }
}

/** CmdLine::parse_and_remove_key matches "key"
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] valueBuf buffer is populated with the substring between key= and the first space delimiter (or end of string)
 * @param[in] valueBufLen limits the size of valueBuf
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 * @post chSeparator is populated with the separator character '=' or '?' following key=, or null character if no separator
 *
 */
bool CmdLine::parse_and_remove_key(const char *key, char *valueBuf, size_t valueBufLen)
{
    // serial().printf("\r\n parse_and_remove_key(\"%s\")...", key);
    // match key inside buf[]?
    for (unsigned int idxSearch = 0; idxSearch < indexOfNextEmptyCell; idxSearch++)
    {
        if (buf[idxSearch] == '\0') {
            // serial().printf("\r\n parse_and_remove_key(\"%s\") no match", key);
            return false; /* no match */
        }
        if (match_is_case_sensitive) {
            // case-sensitive string comparison
            if (buf[idxSearch] != key[0]) { continue; }
        }
        else {
            // case-insensitive string comparison using toupper()
            if (toupper(buf[idxSearch]) != toupper(key[0])) { continue; }
        }
        // possible match; compare buf[idxSearch..] to key[0..]
        unsigned int idxKey = idxSearch; // test whether buf[idxKey..] == key[0..]
        unsigned int idxSeparator = idxSearch; // test whether key=value pair
        unsigned int idxSpace = idxSearch; // end of key=value word
        for (unsigned int offset = 0; offset < strlen(key); offset++)
        {
            if (match_is_case_sensitive) {
                // case-sensitive string comparison
                if (buf[idxKey + offset] != key[offset]) { idxKey = 0; break; }
            }
            else {
                // case-insensitive string comparison using toupper()
                if (toupper(buf[idxKey + offset]) != toupper(key[offset])) { idxKey = 0; break; }
            }
            idxSpace = idxKey + offset + 1;  // assume next char is a word break
            idxSeparator = idxKey + offset + 1; // assume next char is a separator
            if ((buf[idxSeparator] != '=') && (buf[idxSeparator] != '?')) { idxSeparator = 0; }
        }
        if (idxKey == 0) continue; // no match at idxSearch but keep searching
        // ASSERT buf[idxKey..] == key[0..]
        while ((buf[idxSpace] != ' ') && idxSpace < indexOfNextEmptyCell) { idxSpace++; }
        // serial().printf("\r\n parse_and_remove_key(\"%s\") match at index %d length %d, '=' index %d, ' ' index %d",
        //                 key, idxKey, strlen(key), idxSeparator, idxSpace);
        if (idxSeparator != 0) {
            // found key=value: copy buf[idxSeparator+1..' '-1] into valueBuf[0..valueBufLen-1]
            chSeparator = buf[idxSeparator];
            for (unsigned int offset = 0; offset < valueBufLen - 1; offset++)
            {
                if (buf[idxSeparator + 1 + offset] == ' ') break;
                valueBuf[offset] = buf[idxSeparator + 1 + offset];
                valueBuf[offset + 1] = '\0';
            }
        } else {
            // found key but no =value: valueBuf[] = ""
            chSeparator = '\0';
            valueBuf[0] = '\0';
        }
        // on successful match, the key=value should be deleted from cmdbuf
        //serial().printf("\r\n parse_and_remove_key(\"%s\") buf=\"%s\" valueBuf=\"%s\" before deleting key",
        //                key, buf, valueBuf);
        for (unsigned int offset = 0; offset < indexOfNextEmptyCell; offset++)
        {
            unsigned int idxCopyDst = idxKey + offset;
            unsigned int idxCopySrc = idxSpace + 1 + offset;
            if (idxCopyDst > indexOfNextEmptyCell) break;
            if (idxCopySrc > indexOfNextEmptyCell) break;
            buf[idxCopyDst] = buf[idxCopySrc];
        }
        // serial().printf("\r\n parse_and_remove_key(\"%s\") buf=\"%s\" valueBuf=\"%s\" after deleting key",
        //                 key, buf, valueBuf);
        // serial().printf("\r\n parse_and_remove_key(\"%s\") returning true: valueBuf=\"%s\"", key, valueBuf);
        return true;
    }
    // serial().printf("\r\n parse_and_remove_key(\"%s\") no match", key);
    return false; // no match
}

/** CmdLine::parse_frequency_Hz matches "key"=digits
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] frequency_Hz updated from value string if match "key"=value,
 * optional suffix kHz or MHz scales the value by 1000 or 10000000
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 */
bool CmdLine::parse_frequency_Hz(const char *key, uint32_t& frequency_Hz)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (SCLK=\d+(kHZ|MHZ)?)? --> g_SPI_SCLK_Hz
        frequency_Hz = strtoul(valueBuf, NULL, 10);
        if (strstr(valueBuf, "M")) {
            frequency_Hz = frequency_Hz * 1000000;
        }
        if (strstr(valueBuf, "k")) {
            frequency_Hz = frequency_Hz * 1000;
        }
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_interval_usec matches "key"=digits
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] interval_usec updated from value string if match "key"=value,
 * optional suffix Hz kHz or MHz 1/x inverts and scales the value
 * optional suffix s or ms or msec or us or usec scales the value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 */
bool CmdLine::parse_interval_usec(const char *key, us_timestamp_t& interval_usec)
{
    char valueBuf[32];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        interval_usec = strtoul(valueBuf, NULL, 10);
        if (strstr(valueBuf, "ms")) {
            interval_usec = interval_usec * 1000;
        } else if (strstr(valueBuf, "us")) {
            // avoid matching "s" which is a subset of "us"
            // interval_usec = interval_usec * 1;
        } else if (strstr(valueBuf, "s")) {
            interval_usec = interval_usec * 1000000;
        } else if (strstr(valueBuf, "MHz")) {
            interval_usec = 1. / interval_usec;
        } else if (strstr(valueBuf, "kHz")) {
            interval_usec = 1000. / interval_usec;
        } else if (strstr(valueBuf, "Hz")) {
            interval_usec = 1000000. / interval_usec;
        }
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_flag matches "key"=0 or 1
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] nFlagVar variable to be updated by setting or clearing bits specified by nFlagBitMask. 
 *          If match "key"=0 then the nFlagBitMask bits in nFlagVar are cleared.
 *          If match "key"=1 then the nFlagBitMask bits in nFlagVar are set.
 * @param[in] nFlagBitMask bit mask contains binary 1 in the bit to be controlled by the key=value setting
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 */
bool CmdLine::parse_flag(const char *key, uint8_t& nFlagVar, const uint8_t nFlagBitMask)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CPHA=\d)? --> g_SPI_dataMode | SPI_MODE1
        int x = strtoul(valueBuf, NULL, 10);
        if (x)
        {
            nFlagVar |= nFlagBitMask;
        }
        else
        {
            nFlagVar &= ~nFlagBitMask;
        }
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_byte_hex matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] nByteVar updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-16 hexadecimal.
 */
bool CmdLine::parse_byte_hex(const char *key, uint8_t& nByteVar)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            nByteVar = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            nByteVar = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        nByteVar = strtoul(valueBuf, NULL, 16); // default radix hex
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_byte_dec matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] nByteVar updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-10 decimal.
 */
bool CmdLine::parse_byte_dec(const char *key, uint8_t& nByteVar)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_dec take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            nByteVar = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            nByteVar = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        nByteVar = strtoul(valueBuf, NULL, 10); // default radix decimal
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_uint16_hex matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] uint16Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-16 hexadecimal.
 */
bool CmdLine::parse_uint16_hex(const char *key, uint16_t& uint16Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            uint16Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            uint16Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        uint16Var = strtoul(valueBuf, NULL, 16); // default radix hex
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_uint16_dec matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] uint16Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-10 decimal.
 */
bool CmdLine::parse_uint16_dec(const char *key, uint16_t& uint16Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            uint16Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            uint16Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        uint16Var = strtoul(valueBuf, NULL, 10); // default radix decimal
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_int16_hex matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] int16Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-16 hexadecimal.
 */
bool CmdLine::parse_int16_hex(const char *key, int16_t& int16Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            int16Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            int16Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        int16Var = strtoul(valueBuf, NULL, 16); // default radix hex
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_int16_dec matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] int16Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-10 decimal.
 */
bool CmdLine::parse_int16_dec(const char *key, int16_t& int16Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            int16Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            int16Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        int16Var = strtoul(valueBuf, NULL, 10); // default radix decimal
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_uint32_hex matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] uint32Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-16 hexadecimal.
 */
bool CmdLine::parse_uint32_hex(const char *key, uint32_t& uint32Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            uint32Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            uint32Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        uint32Var = strtoul(valueBuf, NULL, 16); // default radix hex
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_uint32_dec matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] uint32Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-10 decimal.
 */
bool CmdLine::parse_uint32_dec(const char *key, uint32_t& uint32Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            uint32Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            uint32Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        uint32Var = strtoul(valueBuf, NULL, 10); // default radix decimal
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_int32_hex matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] int32Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-16 hexadecimal.
 */
bool CmdLine::parse_int32_hex(const char *key, int32_t& int32Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            int32Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            int32Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        int32Var = strtoul(valueBuf, NULL, 16); // default radix hex
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_int32_dec matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] int32Var updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 * Prefix '$' or '0x' or '0X' selects radix base-16 hexadecimal.
 * Default number conversion radix is base-10 decimal.
 */
bool CmdLine::parse_int32_dec(const char *key, int32_t& int32Var)
{
    char valueBuf[16];
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        // ASSERT: buf[matched_index] contains '=' followed by value
        // parse cmdLine arg (CMD=\d)? --> g_I2C_command_regAddress
        // TODO1: parse_byte_hex take hex prefix 0x 0X or suffix $ h H
        if (valueBuf[0] == '$')
        {
            int32Var = strtoul(valueBuf + 1, NULL, 16);
            return true;
        }
        if (valueBuf[0] == '0' && (valueBuf[1] == 'X' || valueBuf[1] == 'x'))
        {
            int32Var = strtoul(valueBuf + 2, NULL, 16);
            return true;
        }
        int32Var = strtoul(valueBuf, NULL, 10); // default radix decimal
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_double matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] doubleVar updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 */
bool CmdLine::parse_double(const char *key, double& doubleVar)
{
    char valueBuf[16];
    char *end;
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        doubleVar = strtof(valueBuf, &end);
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_float matches "key"=value
 *
 * @return true if keyword was found in buffer
 * @param[in] key string value to match
 * @param[out] floatVar updated from value string if match "key"=value
 *
 * @post on successful match, the key=value substring is deleted from cmdbuf
 *
 */
bool CmdLine::parse_float(const char *key, float& floatVar)
{
    char valueBuf[16];
    char *end;
    // bool parse_and_remove_key(const char *key, char *valueBuf, int valueBufLen);
    if (parse_and_remove_key(key, valueBuf, sizeof(valueBuf)))
    {
        floatVar = strtof(valueBuf, &end);
        return true;
    }
    return false; // no match
}

/** CmdLine::parse_byteCount_byteList_hex matches hexadecimal digits separated by spaces.
 * The 0x / 0X prefix is optional. The numbers must be hexadecimal.
 *
 * @return true if more than one hex byte found in buffer
 * @param[out] byteCount is populated with the number of hex bytes found
 * @param[out] mosiDataBuf buffer mosiDataBuf[0..byteCount-1] is populated with the hex bytes found
 * @param[in] mosiDataBufSize limits the number of bytes that will be used
 *
 */
bool CmdLine::parse_byteCount_byteList_hex(size_t& byteCount, char *mosiDataBuf, size_t mosiDataBufSize)
{
    //serial().printf("\r\n parse_byteCount_byteList_hex (buf=\"%s\")...", buf);
    // parse cmdLine hex byte list --> int byteCount; int mosiData[MAX_SPI_BYTE_COUNT];
    byteCount = 0;
    bool got_value = false;
    uint8_t nybbleValue;
    uint8_t temp_value = 0;
    for (unsigned int idxSearch = 0; idxSearch < indexOfNextEmptyCell; idxSearch++)
    {
        if (buf[idxSearch] == '\0') {
            break; // end of buffer
        }
        switch (buf[idxSearch])
        {
            case '0': case '1': case '2': case '3':
            case '4': case '5': case '6': case '7':
            case '8': case '9':
                nybbleValue = buf[idxSearch] - '0';
                temp_value = temp_value * 0x10;
                temp_value = temp_value + nybbleValue;
                got_value = true;
                break;
            case 'a': case 'b': case 'c':
            case 'd': case 'e': case 'f':
                nybbleValue = buf[idxSearch] - 'a' + 0x0a;
                temp_value = temp_value * 0x10;
                temp_value = temp_value + nybbleValue;
                got_value = true;
                break;
            case 'A': case 'B': case 'C':
            case 'D': case 'E': case 'F':
                nybbleValue = buf[idxSearch] - 'A' + 0x0a;
                temp_value = temp_value * 0x10;
                temp_value = temp_value + nybbleValue;
                got_value = true;
                break;
            case 'x': case 'X':
                temp_value = 0;
                break;
            case ' ': case ',':
                if ((got_value) && (byteCount < mosiDataBufSize))
                {
                    //serial().printf("\r\n parse_byteCount_byteList_hex  mosiDataBuf[%d] = 0x%2.2x", byteCount, temp_value);
                    mosiDataBuf[byteCount++] = temp_value;
                    temp_value = 0;
                    got_value = false;
                }
                break;
        }
    } // for idxSearch
    if ((got_value) && (byteCount < mosiDataBufSize))
    {
        //serial().printf("\r\n parse_byteCount_byteList_hex  mosiDataBuf[%d] = 0x%2.2x", byteCount, temp_value);
        mosiDataBuf[byteCount++] = temp_value;
        temp_value = 0;
        got_value = false;
    }
    //serial().printf("\r\n parse_byteCount_byteList_hex (buf=\"%s\") returning: byteCount=%d", buf, byteCount);
    return (byteCount > 0);
}

/** CmdLine::parse_byteCount_byteList_dec matches a list of numbers, separated by spaces.
 * The 0x / 0X prefix may be used to select hexadecimal instead of decimal.
 *
 * @return true if more than one number found in buffer
 * @param[out] byteCount is populated with the number of numbers found
 * @param[out] mosiDataBuf buffer mosiDataBuf[0..byteCount-1] is populated with the numbers found
 * @param[in] mosiDataBufSize limits the number of bytes that will be used
 *
 */
bool CmdLine::parse_byteCount_byteList_dec(size_t& byteCount, char *mosiDataBuf, size_t mosiDataBufSize)
{
    //serial().printf("\r\n parse_byteCount_byteList_dec (buf=\"%s\")...", buf);
    // parse cmdLine hex byte list --> int byteCount; int mosiData[MAX_SPI_BYTE_COUNT];
    byteCount = 0;
    bool got_value = false;
    uint8_t nybbleValue;
    uint8_t temp_value = 0;
    uint8_t radix = 10;
    for (unsigned int idxSearch = 0; idxSearch < indexOfNextEmptyCell; idxSearch++)
    {
        if (buf[idxSearch] == '\0') {
            break; // end of buffer
        }
        switch (buf[idxSearch])
        {
            case '0': case '1': case '2': case '3':
            case '4': case '5': case '6': case '7':
            case '8': case '9':
                nybbleValue = buf[idxSearch] - '0';
                temp_value = temp_value * radix;
                temp_value = temp_value + nybbleValue;
                got_value = true;
                break;
            case 'a': case 'b': case 'c':
            case 'd': case 'e': case 'f':
                nybbleValue = buf[idxSearch] - 'a' + 0x0a;
                radix = 0x10;
                temp_value = temp_value * radix;
                temp_value = temp_value + nybbleValue;
                got_value = true;
                break;
            case 'A': case 'B': case 'C':
            case 'D': case 'E': case 'F':
                nybbleValue = buf[idxSearch] - 'A' + 0x0a;
                radix = 0x10;
                temp_value = temp_value * radix;
                temp_value = temp_value + nybbleValue;
                got_value = true;
                break;
            case 'x': case 'X':
                temp_value = 0;
                radix = 0x10;
                break;
            case ' ': case ',':
                if ((got_value) && (byteCount < mosiDataBufSize))
                {
                    //serial().printf("\r\n parse_byteCount_byteList_dec  mosiDataBuf[%d] = 0x%2.2x", byteCount, temp_value);
                    mosiDataBuf[byteCount++] = temp_value;
                    temp_value = 0;
                    radix = 10;
                    got_value = false;
                }
                break;
        }
    } // for idxSearch
    if ((got_value) && (byteCount < mosiDataBufSize))
    {
        //serial().printf("\r\n parse_byteCount_byteList_dec  mosiDataBuf[%d] = 0x%2.2x", byteCount, temp_value);
        mosiDataBuf[byteCount++] = temp_value;
        temp_value = 0;
        got_value = false;
    }
    //serial().printf("\r\n parse_byteCount_byteList_dec (buf=\"%s\") returning: byteCount=%d", buf, byteCount);
    return (byteCount > 0);
}


// End of file