1. Тема дня
Разбираем обработку отказов и безопасные деградированные режимы:
transient fault
recoverable fault
degraded mode
fatal fault
fault domain
recovery policy
fault snapshot
supervisorГлавная мысль: хорошая прошивка не должна реагировать на любую ошибку одинаково. Иногда надо повторить операцию, иногда отключить одну подсистему, иногда перейти в degraded mode, а иногда действительно перезагрузиться.
2. Зачем это нужно в проектах
В твоих ESP32/STM32-проектах много подсистем, которые могут ломаться независимо:
EC25:
модем не отвечает на AT;
PDP context не поднимается;
MQTT отвалился;
TCP socket завис.
I2C:
ADS1115 не отвечает;
GPIO-expander дал NACK;
шина зависла из-за SDA low.
Светофорные входы:
один канал шумит;
RED и GREEN одновременно активны;
ADC diagnostic не совпадает с GPIO state.
CAN/RS-485:
bus-off;
CRC errors;
frame timeout;
чужой трафик забил шину.Если на всё отвечать reboot(), устройство будет казаться ненадёжным, хотя часто проблема локальная: например, умер ADS1115, но fast path фаз через GPIO всё ещё работает.
3. Теория
Классы ошибок
typedef enum {
FAULT_CLASS_TRANSIENT = 0,
FAULT_CLASS_RECOVERABLE,
FAULT_CLASS_DEGRADED,
FAULT_CLASS_FATAL,
} fault_class_t;Transient fault - кратковременная ошибка, которая может пройти сама: один I2C NACK, один MQTT publish timeout, единичная UART framing error, короткий пропуск AC-импульса. Реакция: счётчик и rate-limited warning, без reset. Recoverable fault - ошибка повторилась, но подсистему можно восстановить: EC25 не отвечает 30 секунд, I2C device не отвечает несколько попыток, UART DMA завис, CAN ушёл в bus-off впервые. Реакция: локальная recovery state machine. Degraded fault - часть системы недоступна, но устройство может выполнять главную функцию: ADS1115 offline, MQTT недоступен, EC25 offline, CAN offline, logger overload. Реакция: пометить подсистему offline, продолжить fast path, периодически пробовать восстановить. Fatal fault - продолжать нельзя или опасно: heap corruption, stack overflow, повреждение критической конфигурации, input_task не работает и recovery не помогла, brownout. Реакция: safe outputs, fault snapshot, reset/halt.
Fault domains
Fault domain - область, внутри которой ошибка сначала лечится локально.
INPUT_DOMAIN:
GPIO input driver
phase_detector
input diagnostics
MODEM_DOMAIN:
UART EC25
AT parser
PDP/MQTT/TCP state machine
I2C_DOMAIN:
ADS1115
GPIO expanders
I2C manager
CAN_DOMAIN:
CAN driver
filters
TX/RX queuesОшибка внутри домена сначала лечится внутри домена. Только если recovery не помогла, fault поднимается выше - к supervisor.
Degraded mode
Degraded mode должен быть явным состоянием:
typedef enum {
SYSTEM_MODE_NORMAL = 0,
SYSTEM_MODE_DEGRADED,
SYSTEM_MODE_RECOVERY,
SYSTEM_MODE_FATAL,
} system_mode_t;Примеры:
MODE_DEGRADED_NO_MODEM:
локально детектируем фазы, но не отправляем через EC25/MQTT.
MODE_DEGRADED_NO_ADC_DIAG:
fast phase detector работает, но analog confidence unavailable.
MODE_DEGRADED_INPUT_CONFLICT:
входы читаются, но текущая фаза помечена conflict/unknown.Degraded mode должен сохранять максимально возможную полезную функцию и явно сообщать, что именно недоступно.
Fault event
typedef enum {
FAULT_SRC_SYSTEM = 1,
FAULT_SRC_INPUT,
FAULT_SRC_PHASE,
FAULT_SRC_MODEM,
FAULT_SRC_MQTT,
FAULT_SRC_I2C,
FAULT_SRC_ADS1115,
FAULT_SRC_GPIO_EXPANDER,
FAULT_SRC_UART,
FAULT_SRC_CAN,
FAULT_SRC_RS485,
FAULT_SRC_POWER,
} fault_source_t;
typedef enum {
FAULT_ACTION_NONE = 0,
FAULT_ACTION_RETRY,
FAULT_ACTION_REINIT_DRIVER,
FAULT_ACTION_RESET_DEVICE,
FAULT_ACTION_MARK_OFFLINE,
FAULT_ACTION_ENTER_DEGRADED,
FAULT_ACTION_SYSTEM_RESET,
} fault_action_t;
typedef struct {
uint32_t seq;
int64_t detected_us;
fault_source_t source;
fault_class_t fault_class;
fault_action_t action;
int32_t error_code;
uint32_t arg0;
uint32_t arg1;
uint32_t arg2;
uint32_t repeat_count;
} fault_event_t;Такой event можно положить в ring buffer, показать через CLI, отправить по MQTT/CAN или сохранить в fault snapshot.
4. Типичные ошибки
- Использовать ESP_ERROR_CHECK() на recoverable ошибках.
- Любую ошибку лечить reboot.
- Делать recovery без backoff и retry limit.
- Не различать “главная функция недоступна” и “диагностика недоступна”.
- Делать recovery внутри ISR/callback.
- Не показывать degraded mode наружу через CLI/MQTT/CAN.
- Не тестировать fault path в HIL.
5. Практическое задание
Создай FAULT_HANDLING_POLICY.md.
# Fault handling policy
## Fault classes
| Class | Meaning | Action |
|---|---|---|
| TRANSIENT | single temporary error | counter + rate-limited log |
| RECOVERABLE | repeated/local error | local recovery |
| DEGRADED | subsystem unavailable, main function partly works | mark offline, continue, report |
| FATAL | unsafe or main function impossible | safe outputs, snapshot, reset/halt |Затем опиши domains, fault codes и CLI-команду faults. Пример CLI:
faults:
SYSTEM_MODE=DEGRADED
degraded_mask=NO_MODEM|NO_ADC_DIAG
recent:
123456.100 I2C ADS1115_OFFLINE class=DEGRADED action=MARK_OFFLINE count=3
123500.250 MODEM NO_RESPONSE class=RECOVERABLE action=RESET_DEVICE count=1
123900.800 PHASE CONFLICT class=TRANSIENT action=NONE count=26. Что попробовать дальше
- Реализовать fault_report() и fault_manager_task.
- Добавить HIL-сценарии: ADS1115 unplug, EC25 no response, RED+GREEN conflict, input_task freeze, CAN bus-off.
- Проверить, что reboot вызывается только для FATAL, а не для любого NACK/timeout.
Задание
ADS1115 перестал отвечать, но GPIO phase detector работает. Определите fault domain, degraded mode, лимит локального восстановления и внешний статус.
Критерии самопроверки: Отделить потерю диагностики от потери главной функции; назвать retry limit/backoff и наблюдаемый статус. Безусловный reboot не удовлетворяет заданию.
Показать ответ автора
Сохранить GPIO fast path, пометить диагностику ADC offline, сообщить деградированный статус и повторять попытки по локальной политике I2C. Передавать отказ выше только после исчерпания ограниченной политики или при опасности для главной функции.
Задание
Спроектируйте HIL-проверку input_task freeze. Укажите внедряемый отказ, ожидаемое решение supervisor и доказательство перехода к safe outputs.
Критерии самопроверки: Записать timeout прогресса и результат recovery, а не только счётчик reboot; объяснить, как проверка доказывает переход к безопасным выходам.
Показать ответ автора
Остановить прогресс input_task в контролируемом низковольтном тесте, наблюдать health/fault policy и ограниченное восстановление, затем проверить safe outputs и fault snapshot при переходе по fatal escalation path.