Embedded HandbookПриложения
Все разделы › XXIII. Приложение к прибору: Dart и Flutter
STM32F103, STM32F407, STM32G431, STM32L431 · уровень средний · 6 мин чтения
Открыть в интерактивном справочнике

190. Телеметрия и выпуск: кольцевой буфер, прореживание, хранение, сборка

Последний раздел части: прибор шлёт показания часами, приложение должно их показывать, хранить и переживать выпуск новой версии. Три вопроса, на которые тут есть числа: что держать в памяти, что рисовать на экране и в чём хранить журнал.

Числа получены пакетом _verify/dart190: пять проверок и ряд на 200 000 точек с одним выбросом.

Как это называется в документации

Оригинал Перевод
ring / circular buffer кольцевой буфер
downsampling, decimation прореживание
min-max decimation прореживание по минимуму и максимуму
retention срок хранения
append-only log журнал только на дозапись
gzip, deflate сжатие
preferences, key-value store настройки: пары «ключ — значение»
build number, version code номер сборки
release build, obfuscation сборка для выпуска, запутывание кода

190.1 Кольцевой буфер: память не растёт

Приложение, которое держит всё пришедшее, падает через сутки. Экрану нужна только недавняя история, поэтому точки кладут в кольцо фиксированного размера:

Что проверено Значение
ёмкость буфера 10 000 точек
подано точек 25 000
осталось в буфере 10 000
первая точка в буфере 15 000-я

Память не растёт вообще: список выделен один раз, новые точки затирают старые. Всё, что нужно сохранить дольше, уходит в журнал на диск — это следующий разговор.


190.2 Прореживание: экран уже, чем данные

На графике шириной 800 точек нет смысла рисовать 200 000 значений. Вопрос в том, как их сократить. Простое «каждая 250-я» быстрое и неправильное:

Способ Точек на экране Выброс 3,3 В
каждая 250-я 800 потерян
минимум и максимум по корзине 1600 на месте

Второй способ смотрит на все точки корзины и берёт из неё две — самую низкую и самую высокую:

for (int j = i; j < end; j++) {
  if (src[j].volts < lo.volts) lo = src[j];
  if (src[j].volts > hi.volts) hi = src[j];
}

Цена — вдвое больше точек на экране и полный проход по данным. Выгода — на графике видно то, ради чего его смотрят: короткий выброс, просадку питания, единичный сбой.

Один выброс на 200 000 точек: что доедет до экрана


190.3 Хранение: двоичный формат, текст и сжатие

Журнал на 100 000 точек, где у каждой время в миллисекундах от 1970 года и напряжение:

Формат Размер, байт
двоичный, 12 байт на точку 1 200 000
текстовый CSV 1 999 999
двоичный после gzip 503 969
текстовый после gzip 205 684

Вывод неожиданный ровно до того момента, как о нём подумаешь: текст больше, но сжимается лучше. Двоичное представление чисел с плавающей точкой почти случайно с точки зрения сжатия, а в тексте повторяются цифры, запятые и одинаковые старшие разряды времени. Для отправки на сервер текст в gzip оказывается в 2,5 раза меньше двоичного в gzip.

Но сравнение неравное. CSV хранит напряжение до милливольта, а число с плавающей точкой — все свои разряды, и у плавной волны младшие из них меняются от точки к точке. Двоичная запись той же точности — милливольты 16-разрядным целым, время приращением от прошлой точки, тоже 16 бит, — занимает 4 байта на точку: 400 000 байт, а после gzip — 3 662. Так мало выходит у гладкой волны без шума; с шумом ±2 мВ, как у настоящего АЦП, — 66 485 байт против 300 916 у текста в gzip, всё равно в 4,5 раза меньше. Выходит, «текст сжимается лучше» — свойство выбранной записи, а не правило: сжатию помогает не текст, а отсутствие лишней точности.

Практический выбор: на устройстве храним двоично (быстро писать, легко читать кусками) и не точнее, чем нужно; отправляем сжатым — текстом, когда на той стороне его должен прочитать любой инструмент, или той же компактной двоичной записью, когда дорог трафик.

Хранение журнала: что меньше до сжатия и после


190.4 Настройки: что хранить рядом с приложением

Адрес прибора, единицы измерения, выбранный канал, порог тревоги — это пары «ключ — значение», и к ним два требования:

Отдельный случай — адрес и имя прибора: их полезно помнить, но показывать пользователю и давать сменить, иначе смена платы превращается в переустановку приложения.


190.5 Выпуск: версия и что проверить перед сборкой

Версия приложения — одна на всё: номер в pubspec.yaml, тот же номер на экране «о программе» и в журнале. Расходятся они мгновенно, если ставить их руками, поэтому номер берут из одного места, а сборку делают командой, а не мышью.

Перед выпуском полезно прогнать ровно то, что разобрано в этой части:

Что Откуда
проверки пакетов и экрана 185–189, dart test и flutter test
сборка для выпуска и номер версии этот раздел
конвейер: сборка на каждое изменение раздел 110
тест на каждую исправленную ошибку раздел 184

И правило из раздела 182, которое дороже всех остальных: заморозить изменения перед выпуском. Правка «на всякий случай» в кандидате — это 7 % шанса новой ошибки без единых ворот впереди.


190.6 Грабли

Хранить все точки. Приложение съедает память за сутки работы и падает именно тогда, когда прибор что-то показал.

Рисовать «каждую N-ю». График красивый и врёт: выброс, ради которого его смотрели, пропал.

Журнал в текст без сжатия. Вдвое больше места и трафика, чем нужно.

Двоичный журнал без описания формата. Через полгода никто не вспомнит, где там время, а где напряжение; описание — в том же репозитории, рядом.

Секреты в настройках. Хранилище настроек читается любым, у кого есть устройство.

Версию ставят руками. На экране одна, в магазине другая, в журнале третья — и разбор случая из поля становится гаданием.


190.7 Итоги

Дальше: раздел 191 «Встраиваемый Linux: когда он нужен и из чего собран образ».

Открыть в интерактивном справочнике