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

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

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

Возьмём три светодиода — красный, жёлтый, зелёный — каждый со своим резистором на свой 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Ничего
py
Курс по теме
Пройдите курс «Python с нуля» — по шагам, с проверкой
8 уроков · ~14 ч · теория, упражнения и экзамен с бейджем
Открыть курс →