Проект: светофор и расписание по времени

Три светодиода превращаются в настоящий светофор, как только мы добавим логику переключения фаз по времени.

Светофор — идеальный первый "умный" проект: тут и несколько устройств, и расписание, и циклическая логика. Всё, как во взрослых системах.

Возьмём три светодиода — красный, жёлтый, зелёный — каждый со своим резистором на свой GPIO-пин. Задача: переключать фазы как настоящий светофор и держать каждую нужное время.

Светофор — это проект, который ты видел тысячи раз по дороге в школу, и именно поэтому он такой удобный для обучения: тебе не нужно объяснять, как он должен работать, ты и так знаешь. Красный держится долго, зелёный тоже, жёлтый — короткое предупреждение между ними, и всё это крутится по кругу без остановки. Когда абстрактное "напиши циклическую логику с таймингами" превращается в знакомый объект на столе, программирование перестаёт быть сухой теорией. А ещё это первый проект, где устройств больше одного — и приходится думать, чтобы они не мешали друг другу.

Как работает под капотом

   GPIO17 --[330]-- (красный)  -- GND
   GPIO27 --[330]-- (жёлтый)   -- GND
   GPIO22 --[330]-- (зелёный)  -- GND

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

   ДАННЫЕ (расписание)        ЛОГИКА (цикл)
   +------------------+       +------------------+
   | красный  - 5с    |  -->  | бери фазу        |
   | зелёный  - 5с    |  -->  | включи цвет      |
   | жёлтый   - 2с    |  -->  | жди длительность |
   +------------------+       | -> следующая     |
   меняем числа здесь         код НЕ меняется

На Pi это выглядело бы так (пример для платы):

from gpiozero import LED
from time import sleep

red, yellow, green = LED(17), LED(27), LED(22)

while True:
    red.on();    sleep(5); red.off()
    green.on();  sleep(5); green.off()
    yellow.on(); sleep(2); yellow.off()

Обрати внимание на важную деталь: перед включением новой фазы предыдущая обязательно гасится (red.off() до того, как загорится зелёный). Если этого не делать, в какой-то момент будут гореть два цвета сразу — а настоящий светофор так себя не ведёт. Управление несколькими устройствами всегда требует следить за тем, чтобы их состояния не конфликтовали.

А логику расписания — какая фаза, как долго и что дальше — отладим в браузере на чистом Python. Это сердце проекта, и оно от железа не зависит. Попробуй сам ▶

# Расписание фаз светофора по времени (в условных секундах)
schedule = [
    ("красный",  5),
    ("зелёный",  5),
    ("жёлтый",   2),
]

clock = 0
for cycle in range(2):                 # два полных круга
    for color, duration in schedule:
        print(f"t={clock:>2}с: горит {color} ({duration}с)")
        clock += duration

print(f"Полный цикл занял {clock} условных секунд")

Мы храним расписание как список пар "цвет — длительность". Меняя числа, легко настроить тайминги, не трогая остальной код. Это и есть хорошая архитектура: данные отдельно, логика отдельно.

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

# Расширенное расписание + подсчёт циклов за минуту
schedule = [
    ("красный",   5),
    ("зелёный",   4),
    ("жёлтый",    2),
    ("пешеходный", 3),
]

cycle_time = sum(duration for _, duration in schedule)
fits = 60 // cycle_time

print(f"Один цикл: {cycle_time}с")
print(f"За минуту уложится полных циклов: {fits}")
for color, duration in schedule:
    print(f"  {color:<10} -> {duration}с")

Частые ошибки

  • Горят два цвета сразу. Перед включением новой фазы выключай предыдущую.
  • Жёсткие числа в коде. Если тайминги "вшиты" повсюду, их тяжело менять — храни в списке/словаре.
  • Перепутаны пины. Проверь, что цвет в коде соответствует физическому светодиоду.
  • Забыли про бесконечность. Настоящий светофор работает по кругу — без while True код отработает один раз и остановится.
  • Слишком короткие фазы. Если поставить длительность в доли секунды, светофор будет "мигать" вместо нормального переключения — тайминги должны быть человеческими.

Best practices

  • Выноси настройки (тайминги, пины) в начало файла или в структуру данных.
  • Сначала отладь последовательность фаз на печати, потом подключай светодиоды.
  • Подумай о расширении: пешеходная фаза, мигающий жёлтый ночью.
  • Давай переменным понятные имена (red, yellow), а не led1, led2 — через неделю спасибо себе скажешь.
  • Проверяй, что сумма фаз даёт разумный полный цикл, прежде чем заливать на железо.

Итоги. Светофор — это несколько устройств плюс расписание фаз, и главное правило — гасить предыдущий цвет перед новым. Расписание удобно хранить данными (список пар) и отлаживать на чистом Python, а уже потом переносить на gpiozero. Разделив данные и логику, ты легко добавляешь новые фазы, не трогая рабочий код. Дальше перейдём к датчикам.

Проверьте себя
1. Почему тайминги светофора лучше хранить в списке пар, а не вшивать числами по всему коду?
AТак быстрее работает
BТак их легко менять в одном месте, не трогая логику — данные отделены от кода
CЭто требование Python
DЧтобы код был длиннее
2. Что важно сделать перед включением новой фазы светофора?
AПерезагрузить Pi
BВыключить предыдущий горящий светодиод
CПоменять резисторы
DНичего