Cutils
Модуль cutils предоставляет набор утилит для работы с кэшами, логами,
бинарными данными, таблицами и путями файловой системы.
Модуль также доступен под именем cacheutils для обратной совместимости.
Кэш целых чисел
cutils.newi(size)
Создать кэш для хранения целых чисел.
Аргументы:
- size: число; размер кэша (1-5000)
Возвращает: userdata кэша
local cache = cutils.newi(100)
cutils.addi(cache, value)
Добавить целое число в кэш.
Аргументы:
- cache: userdata; кэш, созданный через newi()
- value: число; значение для добавления
Возвращает true, если значение добавлено или уже было в кэше. Возвращает
false, если заполненный кэш не может принять новое значение.
local cache = cutils.newi(100)
cutils.addi(cache, 12345)
cutils.addi(cache, 67890)
cutils.findi(cache, value)
Найти целое число в кэше.
Аргументы:
- cache: userdata; кэш
- value: число; искомое значение
Возвращает: boolean; true если найдено, false если нет
local cache = cutils.newi(100)
cutils.addi(cache, 12345)
print(cutils.findi(cache, 12345)) -- true
print(cutils.findi(cache, 99999)) -- false
Кэш строк
cutils.news(size, maxStrLen)
Создать кэш для хранения строк.
Аргументы:
- size: число; количество строк (1-5000)
- maxStrLen: число; максимальная длина строки (1-20)
Возвращает: userdata кэша
local cache = cutils.news(100, 10)
cutils.adds(cache, str)
Добавить строку в кэш.
Аргументы:
- cache: userdata; кэш, созданный через news()
- str: строка; значение для добавления
Возвращает true, если строка добавлена или уже была в кэше. Возвращает
false, если кэш заполнен, строка длиннее maxStrLen или содержит нулевой
байт.
local cache = cutils.news(100, 10)
cutils.adds(cache, "ABC123")
cutils.finds(cache, str)
Найти строку в кэше.
Аргументы:
- cache: userdata; кэш
- str: строка; искомое значение
Возвращает: boolean; true если найдено
local cache = cutils.news(100, 10)
cutils.adds(cache, "ABC123")
print(cutils.finds(cache, "ABC123")) -- true
Логирование
cutils.newLog(size)
Создать буфер для логов.
Аргументы:
- size: число; размер буфера в байтах (1–65536)
Возвращает: userdata лога
local log = cutils.newLog(1024)
cutils.addLog(log, message, level)
Добавить сообщение в лог.
Аргументы:
- log: userdata; лог, созданный через newLog()
- message: строка; текст сообщения
- level: число; уровень лога (cutils.Info, cutils.Warn, cutils.Err)
Функция ничего не возвращает. Когда буфер заполнен, она удаляет самые старые записи. Сообщение, которое вместе со служебными данными не помещается в буфер, не добавляется. Неизвестный уровень логирования также игнорируется.
local log = cutils.newLog(1024)
cutils.addLog(log, "System started", cutils.Info)
cutils.addLog(log, "Low memory", cutils.Warn)
cutils.addLog(log, "Connection failed", cutils.Err)
cutils.printLog(log)
Получить содержимое лога.
Аргументы:
- log: userdata; лог
Возвращает: строка; содержимое лога
local log = cutils.newLog(1024)
cutils.addLog(log, "Hello", cutils.Info)
print(cutils.printLog(log))
Константы уровней логирования
cutils.Info— информационное сообщение (@)cutils.Warn— предупреждение ($)cutils.Err— ошибка (^)
Бинарные функции
cutils.hex(num [, big])
Преобразовать младший байт числа в двухсимвольную шестнадцатеричную строку.
Аргументы:
- num: целое число; используется младший байт
- big: boolean (опционально); true для верхнего регистра
Возвращает: строка; hex-представление
print(cutils.hex(255)) -- "ff"
print(cutils.hex(255, true)) -- "FF"
cutils.hexlify(table [, big])
Преобразовать таблицу байтов в таблицу hex-строк.
Аргументы:
- table: таблица; массив целых чисел от 0 до 255
- big: boolean (опционально); true для верхнего регистра
Возвращает: таблица; массив hex-строк
local bytes = {0xDE, 0xAD, 0xBE, 0xEF}
local hex = cutils.hexlify(bytes, true)
-- hex = {"DE", "AD", "BE", "EF"}
print(table.concat(hex)) -- "DEADBEEF"
cutils.unhexlify(str [, asString])
Преобразовать hex-строку в таблицу байтов.
Аргументы:
- str: строка; hex-строка (чётное количество символов)
- asString: boolean (опционально); true для возврата строки, а не таблицы
Возвращает: таблица или строка (массив байтов)
local bytes = cutils.unhexlify("DEADBEEF")
-- bytes = {222, 173, 190, 239}
cutils.bin(num)
Преобразовать неотрицательное целое число в двоичную строку.
Аргументы:
- num: неотрицательное целое число
Возвращает: строка; двоичное представление
print(cutils.bin(255)) -- "11111111"
print(cutils.bin(10)) -- "1010"
Работа с таблицами
cutils.toBits(num [, bits])
Преобразовать число в таблицу битов (старший бит первый).
Аргументы:
- num: неотрицательное целое число
- bits: число (опционально); количество возвращаемых битов. Если ширина меньше необходимой, старшие биты отбрасываются
Возвращает: таблица; массив битов (0 или 1)
local bits = cutils.toBits(5)
-- bits = {1, 0, 1}
bits = cutils.toBits(5, 8)
-- bits = {0, 0, 0, 0, 0, 1, 0, 1}
cutils.sliceTable(tbl [, first [, last]])
Извлечь срез из таблицы.
Аргументы:
- tbl: таблица; исходная таблица
- first: число (опционально); начальный индекс (по умолчанию 1)
- last: число (опционально); конечный индекс (по умолчанию #tbl)
Возвращает: таблица; новая таблица со срезом
local t = {1, 2, 3, 4, 5}
local slice = cutils.sliceTable(t, 2, 4)
-- slice = {2, 3, 4}
cutils.compareSlices(slice1, slice2)
Сравнивает длину последовательной части таблиц и соответствующие элементы
оператором Lua ==. Вложенные таблицы рекурсивно не сравниваются.
Аргументы:
- slice1: таблица
- slice2: таблица
Возвращает: boolean; true если таблицы равны
local a = {1, 2, 3}
local b = {1, 2, 3}
local c = {1, 2, 4}
print(cutils.compareSlices(a, b)) -- true
print(cutils.compareSlices(a, c)) -- false
Работа с путями
cutils.splitPath(path)
Разбить путь на компоненты.
Аргументы:
- path: строка; путь к файлу
Возвращает: таблица; массив компонентов пути
local parts = cutils.splitPath("/lib/lua/mylib.lua")
-- parts = {"lib", "lua", "mylib.lua"}
cutils.dirname(path)
Получить директорию из пути.
Аргументы:
- path: строка; путь к файлу
Возвращает: строка; путь к директории
print(cutils.dirname("/lib/lua/mylib.lua")) -- "/lib/lua"
print(cutils.dirname("file.txt")) -- "."
print(cutils.dirname("/file.txt")) -- "/"
cutils.basename(path)
Получить имя файла из пути.
Аргументы:
- path: строка; путь к файлу
Возвращает: строка; имя файла
print(cutils.basename("/lib/lua/mylib.lua")) -- "mylib.lua"
print(cutils.basename("file.txt")) -- "file.txt"
cutils.mkdirp(path)
Создать все директории в пути (аналог mkdir -p). Последний компонент пути считается именем файла и не создаётся как директория.
Аргументы:
- path: строка; путь к файлу
Возвращает true при успехе или false, err при системной ошибке. Путь длиной
256 байт и больше вызывает исключение.
-- Создаст директории /lib/lua/mylib/
local ok, err = cutils.mkdirp("/lib/lua/mylib/test.lua")
if not ok then
print("Error: " .. err)
end
Base64
cutils.b64enc(data)
Закодировать данные в Base64.
Аргументы:
- data: строка; бинарные данные для кодирования
Возвращает: строка; Base64 представление
print(cutils.b64enc("Hello World"))
-- "SGVsbG8gV29ybGQ="
-- Бинарные данные
local bin = string.char(0x00, 0x01, 0xFF)
print(cutils.b64enc(bin))
-- "AAH/"
cutils.b64dec(base64)
Декодировать данные из Base64. Декодер принимает строки как с завершающими
знаками =, так и без них, а пробельные и другие посторонние символы
пропускает.
Аргументы:
- base64: строка; Base64 строка
Возвращает: строка; декодированные бинарные данные
print(cutils.b64dec("SGVsbG8gV29ybGQ="))
-- "Hello World"
-- Работает с заполнением и без него
print(cutils.b64dec("SGVsbG8")) -- без =
print(cutils.b64dec("SGVsbG8=")) -- с =
Пример: передача файлов
-- Отправка файла (кодирование)
local function file_to_b64(path)
local f = io.open(path, "rb")
if not f then return nil end
local data = f:read("*a")
f:close()
return cutils.b64enc(data)
end
-- Приём файла (декодирование)
local function b64_to_file(path, b64)
local f = io.open(path, "wb")
if not f then return false end
f:write(cutils.b64dec(b64))
f:close()
return true
end
-- Использование
local encoded = file_to_b64("/test.lua")
b64_to_file("/backup.lua", encoded)
Время
cutils.adjustTz(timeFormat, timeOffset)
Добавляет к текущему системному времени заданное смещение и форматирует
результат через strftime. Это числовое смещение, а не база часовых поясов:
функция не применяет правила перехода на летнее время.
Аргументы:
- timeFormat: строка; формат времени (например, “%Y-%m-%d %H:%M:%S”)
- timeOffset: число; смещение в часах, а не в секундах, относительно системного времени
Возвращает: строка; отформатированное время с учётом смещения
-- Москва (UTC+3), если системное время настроено на UTC
local moscowTime = cutils.adjustTz("%Y-%m-%d %H:%M:%S", 3)
print(moscowTime)
-- Нью-Йорк (UTC-5)
local nyTime = cutils.adjustTz("%Y-%m-%d %H:%M:%S", -5)
print(nyTime)
Антидребезг (debounce)
Бит-параллельный антидребезг входного слова. Каждый бит входа фильтруется
независимо «вертикальным» (bit-sliced) счётчиком-интегратором: чтобы смена была
принята, бит должен продержаться отличным от текущего стабильного состояния в
течение 2^depth выборок подряд; любая выборка, совпавшая со стабильным
состоянием, сбрасывает счётчик этого бита, поэтому дребезг и одиночные глитчи
отсекаются.
cutils.newDebounce([depth [, initial]])
Создать дебаунсер.
Аргументы:
- depth: число (опционально); глубина счётчика 1..16, смена принимается после
2^depthподряд отличающихся выборок (по умолчанию 5, то есть 32 выборки) - initial: число (опционально); начальное стабильное состояние, маска битов (по умолчанию 0)
Возвращает: userdata
-- при цикле опроса 2 мс: 2^5 = 32 выборки ≈ 64 мс дебаунса
local db = cutils.newDebounce(5)
cutils.debounce(db, sample)
Подать одну «сырую» выборку входного слова и получить отфильтрованное состояние.
Аргументы:
- db: userdata; дебаунсер, созданный через newDebounce()
- sample: число; текущее сырое состояние входов (маска битов)
Возвращает три значения:
- stable: число; стабильное (отфильтрованное) состояние всех битов
- rising: число; маска битов, которые на этом вызове перешли 0 → 1 (фронт)
- falling: число; маска битов, которые на этом вызове перешли 1 → 0 (спад)
local db = cutils.newDebounce(5)
while true do
local stable, rising, falling = cutils.debounce(db, pca:read() & 0x3ff)
if (rising >> 0) & 1 == 1 then
print("кнопка 1 нажата")
end
thread.sleepms(2)
end
cutils.debounceReset(db [, state])
Сбросить счётчики дебаунсера и задать стабильное состояние.
Аргументы:
- db: userdata; дебаунсер
- state: число (опционально); новое стабильное состояние (по умолчанию 0)
-- считать дебаунсер «прогретым»: все входы стабильно высокие
cutils.debounceReset(db, 0x3ff)
Замечания:
- поддерживается до 32 входных бит; выборки и возвращаемые маски — обычные целые Lua;
rising/fallingуже содержат только изменившиеся биты — не нужно хранить предыдущее состояние вручную;- глубина задаёт время фильтрации: смена в любую сторону требует
2^depthстабильных выборок подряд.
Интегратор входов с баллами
Интегратор предназначен для более шумных дискретных сигналов, где одиночная
противоположная выборка не должна полностью сбрасывать накопленную историю.
Каждый выбранный бит имеет независимый балл: при значении 1 балл увеличивается
до max_score, при значении 0 уменьшается до нуля.
Для изменения стабильного состояния используются два разных порога:
- переход 0 → 1 выполняется при
score >= rising_threshold; - переход 1 → 0 выполняется при
score <= falling_threshold.
Условие falling_threshold < rising_threshold создаёт гистерезис и не даёт
состоянию переключаться туда-обратно, когда шум удерживает балл около одного
порога.
cutils.newIntegrator(max_score, rising_threshold, falling_threshold [, initial [, mask]])
Создать интегратор для независимой фильтрации до 32 бит.
Аргументы:
max_score: число 1..65535; максимальный балл каждого бита;rising_threshold: число 1..max_score; порог перехода 0 → 1;falling_threshold: число 0..rising_threshold - 1; порог перехода 1 → 0;initial: число (опционально); начальное стабильное состояние, по умолчанию 0;mask: число (опционально); маска обрабатываемых битов, по умолчанию все 32 бита.
Возвращает: userdata интегратора.
При создании баллы установленных битов из initial получают значение
max_score, баллы сброшенных битов — 0. Биты вне mask всегда остаются
сброшенными.
-- десять входов, баллы 0..30, пороги включения/выключения 20 и 10
local filter = cutils.newIntegrator(30, 20, 10, 0, 0x3ff)
cutils.integrate(filter, sample)
Подать одну сырую выборку и обновить баллы всех выбранных битов.
Аргументы:
filter: userdata; интегратор, созданный черезnewIntegrator();sample: число; текущее сырое состояние входов.
Возвращает три значения:
stable: стабильное состояние;rising: маска принятых на этом вызове переходов 0 → 1;falling: маска принятых на этом вызове переходов 1 → 0.
local input_mask = 0x3ff
-- PCA9555 уже инвертирует активные низкие входы через polarity(), поэтому
-- нажатой кнопке соответствует 1.
local initial = pca:read() & input_mask
local filter = cutils.newIntegrator(30, 20, 10, initial, input_mask)
while true do
local stable, rising, falling = cutils.integrate(filter, pca:read())
if rising ~= 0 then
print("нажаты входы", rising)
end
if falling ~= 0 then
print("отпущены входы", falling)
end
thread.sleepms(2)
end
Частота опроса определяет скорость изменения баллов. Например, при периоде
2 мс и rising_threshold = 20 переход из начального нулевого состояния требует
не менее 40 мс непрерывных единичных выборок. При наличии накопленного балла
время может быть меньше.
cutils.integratorReset(filter [, state])
Сбросить накопленные баллы и задать новое стабильное состояние. Баллы битов,
установленных в state, становятся равны max_score; остальные становятся
равны нулю.
cutils.integratorReset(filter, pca:read() & 0x3ff)