editable serial input line buffer

Dependents:   MAX5715BOB_Tester MAX11131BOB_Tester MAX5171BOB_Tester MAX11410BOB_Tester ... more

Revision:
0:4d7de8b5c800
Child:
1:5b33e7447601
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CmdLine.cpp	Thu May 23 22:08:35 2019 +0000
@@ -0,0 +1,697 @@
+// /*******************************************************************************
+// * 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"
+
+CmdLine::CmdLine(Stream& AssociatedSerialPort, const char *Name)
+    : associatedSerialPort(AssociatedSerialPort)
+    , name(Name)
+    , onEOLcommandParser()
+{
+    indexOfNextEmptyCell = 0;
+    memset(buf, 0, COMMAND_BUFFER_LENGTH);
+}
+/** 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
+            diagnostic_led_EOF();
+            //~ main_menu_status(*this);
+            associatedSerialPort.printf("** U+0004 EOT = EOF **"); // immediately echo EOF for test scripting
+            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
+            diagnostic_led_EOF();
+            //~ main_menu_status(*this);
+            associatedSerialPort.printf("** U+001A SUB = EOF **"); // immediately echo EOF for test scripting
+            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
+        case '{':
+            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
+        //
+        // default:
+        case '"':
+        //~ on_immediate_0x22(); // Unicode (U+0022) " QUOTATION MARK
+        //~ break; // handled as immediate command, do not append to buffer
+        case '#':
+        //~ on_immediate_0x23(); // Unicode (U+0023) # NUMBER SIGN = pound sign, hash, crosshatch
+        //~ break; // handled as immediate command, do not append to buffer
+        case '$':
+        //~ 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 '&':
+        //~ on_immediate_0x26(); // Unicode (U+0026) & AMPERSAND
+        //~ break; // handled as immediate command, do not append to buffer
+        case '\'':
+        //~ on_immediate_0x27(); // Unicode (U+0027) ' APOSTROPHE
+        //~ break; // handled as immediate command, do not append to buffer
+        case '(':
+        //~ on_immediate_0x28(); // Unicode (U+0028) ( LEFT PARENTHESIS
+        //~ break; // handled as immediate command, do not append to buffer
+        case ')':
+        //~ 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 ',':
+        //~ 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 ':':
+        //~ on_immediate_0x3a(); // Unicode (U+003A) : COLON
+        //~ break; // handled as immediate command, do not append to buffer
+        case ';':
+        //~ on_immediate_0x3b(); // Unicode (U+003B) ; SEMICOLON
+        //~ break; // handled as immediate command, do not append to buffer
+        case '<':
+        //~ 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 '>':
+        //~ on_immediate_0x3e(); // Unicode (U+003E) > GREATER-THAN SIGN
+        //~ break; // handled as immediate command, do not append to buffer
+        case '?':
+        //~ on_immediate_0x3f(); // Unicode (U+003F) ? QUESTION MARK
+        //~ break; // handled as immediate command, do not append to buffer
+        case '@':
+        //~ on_immediate_0x40(); // Unicode (U+0040) @ COMMERCIAL AT = at sign
+        //~ break; // handled as immediate command, do not append to buffer
+        case '[':
+        //~ on_immediate_0x5b(); // Unicode (U+005B) [ LEFT SQUARE BRACKET
+        //~ break; // handled as immediate command, do not append to buffer
+        case '\\':
+        //~ on_immediate_0x5c(); // Unicode (U+005C) \ REVERSE SOLIDUS
+        //~ break; // handled as immediate command, do not append to buffer
+        case ']':
+        //~ on_immediate_0x5d(); // Unicode (U+005D) ] RIGHT SQUARE BRACKET
+        //~ break; // handled as immediate command, do not append to buffer
+        case '^':
+        //~ on_immediate_0x5e(); // Unicode (U+005E) ^ CIRCUMFLEX ACCENT
+        //~ break; // handled as immediate command, do not append to buffer
+        case '_':
+        //~ on_immediate_0x5f(); // Unicode (U+005F) _ LOW LINE =SPACING UNDERSCORE
+        //~ break; // handled as immediate command, do not append to buffer
+        case '`':
+        //~ on_immediate_0x60(); // Unicode (U+0060) ` GRAVE ACCENT (also called backtick)
+        //~ break; // handled as immediate command, do not append to buffer
+        case '|':
+        //~ on_immediate_0x7c(); // Unicode (U+007C) | VERTICAL LINE
+        //~ break; // handled as immediate command, do not append to buffer
+        case '~':
+        //~ 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;
+    }
+}
+# if HAS_DAPLINK_SERIAL
+CmdLine cmdLine_DAPLINKserial(DAPLINKserial, "DAPLINK");
+# endif // HAS_DAPLINK_SERIAL
+// TODO1: diagnostic: define HAS_MICROUSBSERIAL 0
+//~ #define HAS_MICROUSBSERIAL 0
+# if HAS_MICROUSBSERIAL
+CmdLine cmdLine_microUSBserial(microUSBserial, "microUSB");
+# endif // HAS_MICROUSBSERIAL
+
+/** 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
+ *
+ */
+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 (buf[idxSearch] != key[0]) { continue; }
+        // possible match; compare buf[idxSearch..] to key[0..]
+        unsigned int idxKey = idxSearch; // test whether buf[idxKey..] == key[0..]
+        unsigned int idxEqualSign = 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 (buf[idxKey + offset] != key[offset]) { idxKey = 0; break; }
+            idxSpace = idxKey + offset + 1;
+            idxEqualSign = idxKey + offset + 1;
+            if (buf[idxEqualSign] != '=') { idxEqualSign = 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), idxEqualSign, idxSpace);
+        if (idxEqualSign != 0) {
+            // found key=value: copy buf[idxEqualSign+1..' '-1] into valueBuf[0..valueBufLen-1]
+            for (unsigned int offset = 0; offset < valueBufLen - 1; offset++)
+            {
+                if (buf[idxEqualSign + 1 + offset] == ' ') break;
+                valueBuf[offset] = buf[idxEqualSign + 1 + offset];
+                valueBuf[offset + 1] = '\0';
+            }
+        } else {
+            // found key but no =value: valueBuf[] = ""
+            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_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
+ *
+ */
+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);
+        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
+ *
+ */
+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);
+        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
+ *
+ */
+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);
+        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
+ *
+ */
+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);
+        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
+