https://habr.com/ru/post/441506/До сих пор не обзавелся аккаунтом, да и не хочу. Потому пишу тут.
На работе дорос до воспитания джунов (пока тока один), потому стал больше обращать внимание на такие ошибки.
Ошибка автора статьи можно назвать это преждевременной оптимизацией, но назову ее тупой инстинктивной оптимизацией и вот почему. Первым инстинктивным способом сжатия будет выкинуть все "ненужное", но когда дошло до пакетирования, выяснилось, что мы не знаем границы данных в потоке байт. Тупая — потому, что надо было подумать хотя бы на один шаг вперед. Решение с маркером в 7-м бите тупейшее, возможно даже связано с особенностью схемы управления передатчиком. Но если взять более универсальный и, казалось бы, менее упакованный формат пакета, получим экономию. Маркировать начало пакета можно было бы двумя способами, в зависимости от способа пакетирования. Если нарезка на пакеты идет на стороне приемника, можно применить маркер из нескольких байт. Если пакетирование идет в передатчике, применить IO pin для сигнализации начала/окончания пакета.
Кроме того автор сделал свою схему абсолютно неизменяемой: нельзя добавить новое устройство, нельзя изменить количество каналов, нельзя изменить частоту выборки. При любом изменении у него будет новая разработка практически с нуля.
Рассмотрим более универсальный вариант. Зафиксирован только тип устройств: АЦП. Одна выборка со всех каналов упаковывается как у автора, упаковкой по 10 бит на канал (точнее по bit_per_channel на канал). Выборки выравнены по байту.
В заголовке представлена дополнительная информация:
- start_marker — маркер, который не встретится в пакете
- packet_size — помощь в нарезке пакетов. В случае неполного пакета, например при внезапном отключении устройства, мы упремся в конец файла или входного потока.
- device_id — номер устройства
- timestamp — оставил как у автора, unix timestamp, хотя я бы добавил точности
- channels — сколько каналов АЦП в одной выборке
- bit_per_channel — бит на канал
- record_per_sec — частота опроса, чтобы можно было рассчитать временные точки для записей
- data — сами данные
Данные в любом порядке байт, как понравится. Я бы оставил принятый в телекоммуникациях "сетевой порядок".
struct packet_t {
uint32_t start_marker; // =FFFFFFFF
uint16_t packet_size;
uint16_t device_id;
uint32_t timestamp;
uint8_t channels;
uint8_t bit_per_channel;
uint16_t record_per_sec;
uint8_t data[]
}
Почему маркер FFFFFFFF? Потому, что появление максимального значения АЦП маловероятно и, в случае появления, можно уменьшить значение на 1, это будет не важно, т.к. АЦП и так уперся в свой потолок. Также в заголовке появление последовательности маркера практически исключено, только при: channels = 255 или device_id = 65535.
Посчитаем длину пакета для моего варианта и для автора статьи.
Мой вариант.
Сожрано под заголовок: 16 байт (расточительно!).
Размер выборки равен ceil(6 * 10 / 8) = 8 байт, потери 4 бита на выборку (волосы шевелятся!).
Размер data равен 8 * 100 = 800 байт.
Итого: 816 байт.
У автора.
Данные заняли ceil(6 * 10 * 100 / 8) = 750 (афтар молодец!).
Заголовок 4 байта.
Итого 754 байта исходных и ceil(754 * 8 / 7) = 862 в пакете (как так можно обосраться?).
Мораль: прежде чем кодить и паять, испачкайте пару листков бумаги, это дешевле и быстрее, чем переделывать.
P.S.: если говорить о реальном устройстве, необходимо еще возможность синхронизировать время на устройстве и сообщать этот факт в протоколе.