Текстовые команды: модуль 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: p1–p5 становятся выходами,
а p6–p10 — входами.
Команды:
get N— читает уровень выводаN(1..9—hw.p1..hw.p9,0—hw.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.p1…hw.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».