# Теория проекта Ambilight: от фотона на экране до фотона из светодиода

> Документ для преподавателя/наставника: что именно происходит в каждой строке кода
> на уровне физики, математики и архитектуры, и какую пользу из этого можно вытащить
> для занятия — не только "сделали мигающую ленту", а реально перевариваемые концепции.

## 1. Общая архитектура: конвейер обработки сигнала

Любая "умная" система ввода-вывода — это четырёхзвенный конвейер:

```
ИСТОЧНИК СИГНАЛА  →  ОБРАБОТКА/МАТЕМАТИКА  →  ПРОТОКОЛ ПЕРЕДАЧИ  →  АКТУАТОР
  (экран/звук)        (фильтры, гамма)         (UART, чексумма)      (LED-лента)
```

Ключевая методическая мысль: **звенья конвейера независимы**. Если правое звено
(ESP32 + лента) не знает и не должно знать, откуда взялся цвет — из захвата экрана,
из микрофона или из случайного генератора — значит, архитектура построена правильно.
Именно поэтому в разделе 6 заменить "источник" на микрофон можно вообще без изменения
кода ESP32: меняется только Python-скрипт до того места, где формируется `led_packet`.
Это и есть на практике принцип **разделения ответственности (separation of concerns)** —
одна из главных идей в инженерии ПО, и его проще всего объяснить именно на таком
наглядном проекте, а не на абстрактном примере.

## 2. Математика дискретизации экрана

Экран — это сетка пикселей с RGB-значениями. Лента — это 60 точек. Значит, нам нужно
"сжать" миллионы пикселей в 60 чисел. Здесь происходит две математические операции:

**2.1. Усреднение как фильтр низких частот**

`zone.mean(axis=(0,1))` — это не просто "взять цвет", это **пространственное усреднение**,
которое физически работает как сглаживающий (low-pass) фильтр: одиночный яркий пиксель
(например, курсор мыши) "размазывается" по всей зоне и почти не влияет на итоговый цвет.
Без усреднения (если бы мы брали один конкретный пиксель) лента бы дрожала от каждого
шевеления курсора — это и есть разница между "взять образец" и "отфильтровать сигнал".

**2.2. Прореживание (даунсэмплинг) — `[::4]`**

`screenshot[y_start:y_end:4, x_start:x_end:4]` — мы не усредняем ВСЕ пиксели зоны, а берём
каждый 4-й по обеим осям. Это снижает количество обрабатываемых данных в 16 раз (4×4),
почти не теряя в качестве среднего цвета — потому что соседние пиксели на экране сильно
коррелируют между собой (если один синий, соседний с вероятностью почти 100% тоже похож).
**Практическая ценность:** это ровно тот же принцип, на котором работает сжатие видео
и понижение частоты дискретизации звука — "достаточно редкая выборка не теряет смысл,
если сигнал гладкий".

## 3. Гамма-коррекция: когда линейная математика обманывает глаз

PWM-скважность светодиода физически линейна по мощности: 50% скважности = 50% от
максимальной световой энергии. Но человеческий глаз воспринимает яркость по степенному
закону (приблизительно `perceived = physical^(1/γ)`, γ≈2.2) — то есть **физически линейный
сигнал выглядит для нас нелинейным**. Поэтому в коде есть LUT (lookup table):

```python
_gamma_lut[i] = round(((i/255) ** (1/GAMMA)) * 255)
```

Это классический пример того, как один и тот же физический параметр (яркость) нужно
**линеаризовать под восприятие**, а не под физику прибора — то же самое происходит
в фотографии (sRGB-кодирование), в калибровке мониторов и в звуке (децибелы — это
тоже логарифмическая, "перцептивная" шкала, а не линейные ватты).

## 4. Протокол передачи: UART как канал с шумом

**4.1. Кадрирование (framing).** Заголовок `'A','d','a'` — это маркер начала кадра.
Без него приёмник (ESP32) не может понять, где начинается новый набор данных в потоке
байт — UART физически передаёт просто последовательность байт без понятия "начало/конец
сообщения". Это азбука протоколов связи: **любой протокол прежде всего должен решить
проблему синхронизации**, прежде чем говорить о содержимом данных.

**4.2. Контроль целостности — XOR-чексумма.** `checksum ^= byte` для каждого байта —
простейший код обнаружения ошибок. Математически XOR — операция "чётности": чексумма
гарантированно покажет несовпадение, если изменилось **нечётное** число бит в кадре,
но может не заметить ошибку, если изменилось чётное число бит сразу в нескольких местах
(редкий, но реальный случай). **Точка для углублённого занятия:** это ровно то место,
где можно показать продвинутым ученикам CRC (Cyclic Redundancy Check) — тот же принцип
обнаружения ошибок, но математически надёжнее (используется в Ethernet, ZIP, USB).

**4.3. Бюджет канала.** UART на N бод передаёт примерно N/10 байт/сек (8 бит данных +
старт/стоп-биты). Кадр в 244 байта при 60 кадрах/сек требует 14 640 байт/сек — это
прямое применение формулы "пропускная способность = объём данных × частота", той же,
что лежит в основе любых расчётов сетевой инфраструктуры или сжатия видео.

## 5. Протокол адресных светодиодов и электрическая теория

SK6812 получает данные по одному проводу через **тайминг импульсов**, а не через
уровень напряжения как таковой: "0" и "1" различаются длительностью высокого сигнала
в импульсе примерно 1.25 мкс. Это делает протокол чувствительным к:

- **Уровню логики** — порог распознавания "1" у 5V-чипа выше, чем может надёжно выдать
  3.3V-контроллер без согласования уровней (см. раздел про вспышки выше).
- **Электропитанию** — конденсатор (440 мкФ у тебя на ленте) гасит низкочастотные скачки
  тока при смене яркости/цвета: каждый светодиод при включении на максимум резко
  "запрашивает" ток у источника, и без буферной ёмкости это просаживает напряжение
  по всей цепочке.
- **Общей земле (GND)** — сигнал "0V" для приёмника имеет смысл только относительно
  ОБЩЕЙ точки отсчёта. Если у источника данных и у питания ленты разные, не соединённые
  между собой "нули" — сигнал плывёт. Это та же идея, что и заземление в любой
  электронике, просто на бытовом проекте она становится наглядной и осязаемой.

## 6. Расширение на звук: честный разговор про "распознавание эмоций"

Architecturally — да, заменить источник на микрофон легко (раздел 1). Но важно различать
два уровня сложности, и это отличная тема для критического мышления на занятии:

**Уровень А — "audio-reactive" (просто и честно).** Громкость (RMS-амплитуда сигнала)
→ яркость; высота тона (через автокорреляцию или БПФ — поиск основной частоты F0)
→ оттенок цвета. Это легитимная, понятная, действительно работающая обработка сигнала —
и хороший повод объяснить, что такое FFT (преобразование Фурье: "любой сложный звук
раскладывается в сумму простых синусоид разной частоты").

**Уровень Б — "распознавание эмоций" (на самом деле сложно и спорно).** Настоящее
Speech Emotion Recognition — это модели машинного обучения, обученные на размеченных
датасетах (RAVDESS, IEMOCAP и т.п.), точность которых даже в лабораторных условиях
обычно 60-75%, а "в дикой природе" (другой голос, другой микрофон, другой язык) —
заметно ниже. **Методическая ценность:** это прекрасный пример для разговора с учениками
о том, что "ИИ определяет эмоции" — это упрощение, которое легко продать как магию, но
за которым стоят вполне измеримые (и не идеальные) числа точности. Хороший проект для
старших учеников — НЕ "верить" готовому ярлыку эмоции, а самим построить простой
классификатор и честно измерить его ошибку.

Это разница между "лента мигает в такт голосу" (честно, работает, объяснимо) и
"лента знает, что ты злишься" (преувеличение, которое стоит разобрать критически).

## 7. Практическая ценность — зачем это всё в реальной жизни

| Концепция из проекта | Где встречается в индустрии |
|---|---|
| Усреднение/прореживание сигнала | Сжатие видео/звука, обработка сенсоров IoT |
| Гамма-коррекция / линеаризация | Калибровка дисплеев, фотография, аудио (дБ) |
| Кадрирование протокола + чексумма | Любой сетевой протокол, прошивки, телеметрия |
| Бюджет пропускной способности | Проектирование сетей, выбор интерфейсов (UART/SPI/I2C/Ethernet) |
| Согласование логических уровней | Любое сопряжение разных by-питанию чипов (3.3V/5V/12V) |
| Архитектура "источник ⇄ актуатор" | Микросервисы, драйверы устройств, MVC |
| FFT / анализ звука | Распознавание речи, музыкальные анализаторы, медицинские сенсоры |

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

## 8. История отладки: как выглядят реальные инженерные баги

Текущая реализация (checksum, `Adafruit_NeoPixel`, watchdog обрыва связи) — не
первая версия проекта. Ранняя версия работала на других решениях, и разбор того,
как и почему они были заменены, — хороший материал для урока о разнице между
"работает на столе" и "работает надёжно".

**8.1. Пропускная способность UART как невидимый баг.** Ранняя версия использовала
115200 бод. Кадр в 244 байта (3 заголовка + 60×4 GRBW + 1 checksum) при цели 60
кадров/сек требует ≈14.6 КБ/с — а канал на 115200 бод физически прокачивает
только ≈11.5 КБ/с, то есть на 27% меньше нужного. Скрипт "думал", что шлёт 60
FPS, а реально упирался в потолок ≈47 FPS — буфер ОС постепенно копился, и лента
со временем начинала "подвисать" относительно картинки на экране. Баг был
невидим в коде: ни одна строка не была логически неверна, проблема была в
физическом бюджете канала, который никто явно не посчитал. Решение — поднять
`BAUDRATE` до 460800 (см. `README.md`) синхронно в Python и в `Serial.begin()`.

**8.2. RGBW через 3-канальную библиотеку — арифметическое совпадение, а не
архитектура.** До перехода на `Adafruit_NeoPixel` лента управлялась через
`FastLED`, которая рассчитана на 3-байтные структуры `CRGB` (WS2812B), а SK6812 —
честный 4-канальный чип (G,R,B,W). Обходной приём выставлял `NUM_LEDS =
(60*4+2)/3 = 80`, и это "работало" только потому, что `80*3 = 240` случайно
совпадает с `60*4 = 240`. При другом числе светодиодов, например 50, `200/3` —
не целое число, и байты начали бы "расползаться" между соседними пикселями
(зелёный одного светодиода становится красным у соседнего). Урок: код может
пройти визуальную проверку ("лента же светится!") и всё равно быть построен на
случайности, которая ломается при первом же изменении параметра.

**8.3. Баг-репорт: случайные вспышки на статичной картинке.** Показательный
пример дедукции по имеющимся данным. Наблюдение: картинка на экране статична,
значит Python шлёт почти один и тот же кадр раз за разом. Кадр защищён
чексуммой — значит, повреждённый байт на участке Python→ESP32 был бы просто
отброшен целиком (лента держала бы старый цвет, а не вспыхивала). Раз вспышки
всё равно происходят, это сильный признак, что проблема физически на участке
ESP32→лента, где протокол SK6812 однонаправленный и никакой чексуммой не
защищён (см. раздел 5). В реальном случае причиной оказался отвалившийся
контакт общего провода GND между ESP32 и питанием ленты — то есть пункт 2 из
списка Troubleshooting в README, а не пункт 1 (согласование уровней), хотя оба
дают визуально одинаковый симптом. Методическая ценность: при отладке
электроники симптом редко указывает на причину напрямую — нужно исключать
гипотезы по цепочке, начиная с самой быстро проверяемой (общий провод
проверяется за 10 секунд).