1. Тема дня
Идея: записывать не произвольный лог, а входные события de- terministic core, чтобы потом точно воспроизвести поведение FSM на ПК.
REAL DEVICE:
GPIO / ADC decisions / UART EC25 / MQTT / timers
-> normalized events
-> production FSM + event recorder
-> replay file
LINUX HOST:
replay file
-> same FSM/core
-> deterministic resultГлавная мысль: не нужно эмулировать весь ESP32, EC25 и интернет; нужно повторно подать в чистую логику ту же последовательность значимых событий.
2. Зачем это нужно
Редкая полевая ошибка EC25 может зависеть от порядка:
+CEREG:0
MQTT socket lost
AT timeout
+CEREG:1
late OKОбычный лог не всегда позволяет восстановить порядок. Replay- файл позволяет запустить тот же сценарий на ПК и превратить ошибку в regression test.
3. Теория
Что записывать
Записываем inputs в core:
MODEM_LINE
MODEM_URC
MQTT_CONNECTED
MQTT_DISCONNECTED
COMMAND_RECEIVED
GPIO_EDGE
ADC_DECISION
CAN_FRAME
TIMER_EXPIRED
POWER_STATE_CHANGED
CONFIG_LOADEDНе нужно записывать внутренности RTOS: task switch, mutex taken, malloc, printf.
Timer — тоже событие
FSM не должна спрашивать реальный esp_timer_get_time(). Timer service генерирует TIMER_EXPIRED. Replay просто подаёт записанный event.
Logical time
Event содержит mono_us, но replay не должен ждать реальные 40 секунд. Он может мгновенно установить logical clock на times- tamp события.
Sequence важнее timestamp
Timestamp может совпасть у нескольких событий. Основной порядок задаёт sequence. Fragmentation сохраняется
Если тестируется parser, нужно сохранить chunk boundaries:
callback 17 bytes
callback 31 bytes
callback 4 bytesИначе parser bug может исчезнуть.
Outputs как assertions
Можно записывать expected actions:
INPUT: CEREG_REGISTERED
OUTPUT: SEND_AT QIACTReplay подаёт stimuli, а observations использует как assertions.
State hash
После каждого события можно считать hash canonical state pro- jection, не сырой C-структуры.
4. Типичные ошибки
- Записывать текст вместо событий.
- Записывать только ошибки, а не pre-trigger историю.
- Не сохранять sequence.
- Использовать real time в replay.
- FSM сама читает UART/GPIO/timer.
- FSM сама делает MQTT publish.
- На ПК использовать Python-модель вместо production C core.
- Не сохранять UART/TCP fragmentation при parser replay.
- Replay raw ADC, когда нужен только FSM replay.
- Не version’ировать event schema.
- Хешировать сырые structs.
- Не проверять invariants после каждого события.
- Recorder сам ломает timing.
- Писать каждое событие сразу во Flash.
- Записывать секреты в replay artifact.
5. Практическое задание
Сделай replay для modem_core с событиями:
typedef enum {
MODEM_EV_AT_OK = 1,
MODEM_EV_AT_ERROR,
MODEM_EV_CEREG_CHANGED,
MODEM_EV_TIMER_EXPIRED,
} modem_event_type_t;
typedef struct {
uint32_t sequence;
uint64_t mono_us;
modem_event_type_t type;
int32_t argument;
} modem_replay_event_t;Сначала используй текстовый replay v0:
1 0 CEREG 1
2 1000 TIMER 10
3 1100 OK 0
4 1200 CEREG 0
5 4200 TIMER 11
6 4300 CEREG 1
7 4400 OK 0После каждого event выводи state dump и проверяй invariants. Сделай сценарий late OK: timeout старой transaction, новая trans- action, потом AT_OK старой transaction. Если текущая модель не различает transaction ID, добавь его.
6. Что попробовать дальше
- Записывать .evlog на Raspberry Pi через diagnostic UART.
- Конвертировать field replay в fuzz seed.
- Добавить differential replay: старый modem FSM vs новый mo- dem FSM после refactoring.
Задание
Parser получил callbacks на 17, 31 и 4 bytes. Почему replay одним объединённым callback на 52 bytes может не воспроизвести сбой? Назовите две проверки для точного replay.
Критерии самопроверки: Объясните зависимость от fragmentation и сохраните порядок плюс output/invariant checks.
Показать ответ автора
Объединение меняет fragmentation и может убрать bug на границе chunks. Сохраните callbacks 17/31/4 bytes и sequence событий; после каждого event сравнивайте ожидаемые actions и проверяйте invariants в том же core. Logical time может сразу переходить к записанному timestamp.