Перейти к содержанию
Материалы раздела

Текстовые команды: модуль cmd

Модуль cmd упрощает командные строки и короткие текстовые протоколы. Он собирает входную строку, последовательно разбирает её части и отправляет ответ через тот же канал — USB CDC или UART.

Как это работает

cmd:

  • читает байты из выбранного источника;
  • копит одну текущую строку во внутреннем буфере;
  • считает команду завершённой, когда получает CR или LF;
  • при включённом cmd.echo(true) сразу возвращает входящие байты в тот же канал;
  • запоминает ошибку разбора до вызова cmd.done().

Ёмкость строки доступна в константе cmd.buffer_capacity. Сейчас она равна 64 байтам. Константы cmd.cdc и cmd.uart — не размеры буфера, а номера источников:

  • cmd.cdc == 0;
  • cmd.uart == 1.

По умолчанию модуль читает USB CDC. Источник выбирает cmd.source(...), ответ передаёт cmd.write(...), а повтор вводимых символов включает cmd.echo(...).

API

cmd.begin() -> bool

Пытается получить текущую команду.

  • true: строка уже готова к разбору;
  • false: строки пока нет целиком.

Типичный шаблон:

if cmd.begin() then
    -- разбор команды
end

cmd.source(source)

Переключает источник входных байтов для следующего cmd.begin().

Поддерживаемые значения:

  • cmd.cdc — читать из входного буфера USB CDC;
  • cmd.uart — читать из входного буфера UART.

При смене источника незавершённая строка сбрасывается.

cmd работает только со строками, разделёнными CR или LF. Если у протокола другие границы сообщений, принимайте его напрямую через hw.cdc_read() или hw.uart_read().

cmd.write(strconst)

Пишет ответ в выбранный канал:

  • при cmd.cdc — в USB CDC;
  • при cmd.uart — в UART.

Так один обработчик может работать и по CDC, и по UART без ветвления между hw.cdc_write(...) и hw.uart_write(...).

cmd.echo(bool)

Включает или выключает автоматический повтор входящих байтов во время cmd.begin().

  • по умолчанию повтор выключен;
  • при true пользователь видит вводимые символы;
  • ответы протокола функция не дублирует;
  • завершающий CR или LF тоже повторяется.

Обычно это полезно для UART-командной строки:

cmd.source(cmd.uart)
cmd.echo(true)

cmd.word(strconst) -> bool

Сравнивает заданный фрагмент с текстом в текущей позиции, предварительно пропустив пробелы и табуляции.

  • при успехе двигает курсор дальше;
  • при несовпадении просто возвращает false;
  • несовпадение не считается ошибкой разбора.

Сравнение идёт по префиксу, а не по отдельному слову. Например, cmd.word("set") совпадёт с началом settings. Если граница важна, проверьте оставшуюся часть команды через следующий разбор и cmd.done().

if cmd.word("set") then
    -- команда set
elseif cmd.word("adc") then
    -- команда adc
end

cmd.int() -> int

Читает десятичное целое со знаком.

Поддерживаются формы:

  • 123;
  • +123;
  • -123.

Если формат неверен или число не помещается в диапазон uLua:

  • возвращается 0;
  • ошибка запоминается до cmd.done().

cmd.hexbyte() -> int

Читает ровно две шестнадцатеричные цифры и возвращает число 0..255.

Если формат плохой:

  • возвращается 0;
  • ошибка запоминается до cmd.done().

cmd.ok() -> bool

Показывает, что при разборе текущей команды пока не было ошибок.

Это полезно после серии cmd.int() или cmd.hexbyte(), если нужно сразу прервать обработку.

cmd.done() -> bool

Завершает разбор текущей строки.

Он:

  • пропускает пробелы и символы табуляции в конце строки;
  • проверяет, что строка разобрана до конца;
  • возвращает true, если ошибок нет;
  • возвращает false, если были ошибки или лишние символы;
  • всегда освобождает текущую строку.

После cmd.done() нужно начинать заново с cmd.begin().

Рекомендуемый шаблон

Функции не ждут данные. Вызывайте их, когда строка уже могла прийти, или проверяйте ввод в цикле.

if cmd.begin() then
    if cmd.word("set") then
        local ch = cmd.int()
        local val = cmd.int()

        if cmd.done() then
            cmd.write("OK\n")
        else
            cmd.write("ERR syntax\n")
        end

    elseif cmd.word("adc") then
        local ch = cmd.int()

        if cmd.done() then
            cmd.write("OK\n")
        else
            cmd.write("ERR syntax\n")
        end

    else
        cmd.done()
        cmd.write("ERR unknown\n")
    end
end

Пример простого протокола

Поддержим две команды:

  • set <channel> <value>;
  • adc <channel>.
hw.gpio_init(hw.p1, hw.out)
hw.adc_start(hw.p5)

if cmd.begin() then
    if cmd.word("set") then
        local ch = cmd.int()
        local val = cmd.int()

        if cmd.done() then
            if ch == 1 then
                hw.gpio_set(hw.p1, val)
                cmd.write("OK\n")
            else
                cmd.write("ERR channel\n")
            end
        else
            cmd.write("ERR syntax\n")
        end

    elseif cmd.word("adc") then
        local ch = cmd.int()

        if cmd.done() then
            if ch == 5 then
                cmd.write(hw.adc_read(hw.p5) .. "\n")
            else
                cmd.write("ERR channel\n")
            end
        else
            cmd.write("ERR syntax\n")
        end

    else
        cmd.done()
        cmd.write("ERR unknown\n")
    end
end

Те же команды через UART

hw.gpio_init(hw.p2, hw.uart_tx)
hw.gpio_init(hw.p6, hw.uart_rx)
hw.uart_setup(115200, 8, hw.parity_none, 1)
cmd.source(cmd.uart)
cmd.echo(true)

if cmd.begin() then
    if cmd.word("ping") then
        if cmd.done() then
            cmd.write("pong\n")
        else
            cmd.write("ERR syntax\n")
        end
    else
        cmd.done()
        cmd.write("ERR unknown\n")
    end
end

Пример AT-команд

Поддержим два варианта:

  • ATM — без аргумента;
  • ATM+123 — с целочисленным аргументом.
if cmd.begin() then
    if cmd.word("ATM+") then
        local val = cmd.int()

        if cmd.done() then
            cmd.write("OK val=" .. val .. "\n")
        else
            cmd.write("ERR syntax\n")
        end

    elseif cmd.word("ATM") then
        if cmd.done() then
            cmd.write("OK\n")
        else
            cmd.write("ERR syntax\n")
        end

    else
        cmd.done()
        cmd.write("ERR unknown\n")
    end
end

Более длинный вариант ATM+ проверяется первым. Это важно: cmd.word() ищет префикс, поэтому проверка ATM первой перехватила бы обе команды.

Пример управления GPIO по CDC

Пример реализует основные команды GPIO Extender: p1p5 становятся выходами, а p6p10 — входами.

Команды:

  • get N — читает уровень вывода N (1..9hw.p1..hw.p9, 0hw.p10);
  • set N M — устанавливает уровень M на выходе N (N = 1..5);
  • rled M — управляет красным светодиодом (M = 0 или 1);
  • gled M — управляет зелёным светодиодом (M = 0 или 1).
for i = 1, 5 do hw.gpio_init(hw.pin(i), hw.out) end
for i = 6, 9 do hw.gpio_init(hw.pin(i), hw.inp) end
hw.gpio_init(hw.pin(0), hw.inp)

while true do
    if cmd.begin() then
        if cmd.word("get") then
            local n = cmd.int()
            if cmd.done() then
                if n >= 0 and n <= 9 then
                    cmd.write(hw.gpio_get(hw.pin(n)) .. "\n")
                else
                    cmd.write("ERR pin\n")
                end
            else
                cmd.write("ERR syntax\n")
            end

        elseif cmd.word("set") then
            local n = cmd.int()
            local m = cmd.int()
            if cmd.done() then
                if n >= 1 and n <= 5 then
                    hw.gpio_set(hw.pin(n), m)
                    cmd.write("OK\n")
                else
                    cmd.write("ERR pin\n")
                end
            else
                cmd.write("ERR syntax\n")
            end

        elseif cmd.word("rled") then
            local m = cmd.int()
            if cmd.done() then
                hw.rled(m ~= 0)
                cmd.write("OK\n")
            else
                cmd.write("ERR syntax\n")
            end

        elseif cmd.word("gled") then
            local m = cmd.int()
            if cmd.done() then
                hw.gled(m ~= 0)
                cmd.write("OK\n")
            else
                cmd.write("ERR syntax\n")
            end

        else
            cmd.done()
            cmd.write("ERR unknown\n")
        end
    end
end

hw.pin(1)hw.pin(9) соответствуют hw.p1hw.p9, а hw.pin(0)hw.p10. Функции GPIO принимают значение типа pin, поэтому передавать им обычное число нельзя.

Практические советы

  • Всегда завершайте разбор через cmd.done(), даже если команда неизвестна.
  • cmd.word("...") == false само по себе не означает ошибку.
  • После cmd.int() или cmd.hexbyte() можно проверить промежуточный результат через cmd.ok().
  • cmd.echo(true) удобно для терминала, но в обмене между программами повтор обычно мешает.
  • Если строка длиннее cmd.buffer_capacity, cmd.done() вернёт false.
  • В консольном режиме последовательность Ctrl-B U L (байты 02 55 4C) переключает устройство в режим загрузки байт-кода и не попадает в строку cmd. Одиночный Ctrl-B, за которым нет U L, возвращается во входной поток.

Описание CDC, UART и функции hw.pin() находится в разделе «Оборудование: модуль hw».