В этой статье мы покажем вам небольшой домашний креатив и расскажем, как своими руками собрать билд-светофор (и не только билд-) в домашних условиях с использованием почти подручных средств. Для того, чтобы повторить то, что описано будет в статье вам необходима любая отладочная плата на базе ESP32 (хотя не обязательно именно ESP32) открытой прошивкой на базе MicroPython.
Наличие в плате MicroPython обязательно, ведь именно он сделает большую часть работы за нас и именно им мы будем управлять через D через последовательный порт.
Для начала работы желательно собрать корпус, который будет в себе содержать плату с тремя светодиодами — красным, желтым и зеленым, и который станет фактически основой нашего прибора. Строго говоря, корпуса как такового у нас не было, и не было даже материала, а также чертежей, поэтому мы собрали корпус из детского картона, покрасив его с помощью маркера со всех сторон в черный цвет. Также мы покрасили и внутренности светофора в черный цвет, а также фанерную плашку, на которой разместилась вся схема соединения светодиодов. Принимая такое положение, становится понятно, что чертежей для сборки у нас не было — и делали мы все просто на глаз, проектируя светофор под размеры обычной макетной платы, что практически означает то, что при повторении данной конструкции вы не ограничены ничем кроме имеющихся материалов и своих собственных размеров. Сами светодиоды были соединены между собой своими короткими выводами (они собраны в единый провод, который используется для подключения к контактам платы GND), а каждый длинный контакт заканчивался своим проводом, который выходил из самого корпуса и присоединялся к макетному проводу соответствующего цвета.
В нашем случае выглядит это вот так:

Как видите, все собрано из подручных материалов и с минимальными усилиями, и в итоге, получается корпус из которого выходит три провода под контакты светодиодов и один четвертый, который соединяется с землей платы. Эти три контакта (у нас они для удобства помечены каждый в соответствии с цветом своего светодиода) соединяются с контактами платы ESP32 (которая у нас в Arduino формате, то есть выглядит также как Arduino Uno) следующим образом:
- красный с выводом IO13;
- желтый с выводом IO12;
- зеленый с выводом IO5
Эти выводы идут друг за другом, чем мы и воспользовались, но что еще важно, так то, что в MicroPython через который мы будем ими управлять, к этим пинам можно обращаться просто по их номерам: 13, 12 и 5.
Сама плата соединяется с компьютером через обычный micro-USB провод, что означает, что сам ESP32 способен общаться с компьютером через встроенный USB-COM конвертер. Более того, через последовательный порт сам MicroPython доступен в интерактивном режиме и это значит что мы можем пересылать высокоуровневые команды и видить их исполнение в реальном времени !
А именно это нам и надо.
Предварительная подготовка на сборке корпуса и схемы заканчивается, и поэтому, для следующего шага нам потребуется компилятор D с установленным dub, а также библиотека serialport. Поскольку нужна только одна внешняя библиотека и код очень просто, то вместо создания папки под проект, мы создаем однофайловый dub-скрипт:
#!/usr/bin/env dub /+ dub.sdl: dependency "serialport" version="~>2.2.3" +/ import std.process : executeShell; import std.stdio : writeln; import std.string : format, join; import serialport; class TrafficLightControl(ubyte RED_PIN, ubyte YELLOW_PIN, ubyte GREEN_PIN) { private { enum string INITIAL_SETUP_CMD = `from machine import Pin; RED = Pin(%d, Pin.OUT); YELLOW = Pin(%d, Pin.OUT); GREEN = Pin(%d, Pin.OUT)` .format(RED_PIN, YELLOW_PIN, GREEN_PIN); bool RED, YELLOW, GREEN; SerialPortNonBlk _sp; ubyte[] _cmd; void setLED(bool status, string name) { string r = status ? (name ~ `.on();`) : (name ~ `.off();`); _cmd = cast(ubyte[]) r ~ 13; _sp.write(_cmd); _cmd = []; } } void lightsOff() { RED = false; YELLOW = false; GREEN = false; } void lightsOn() { RED = true; YELLOW = true; GREEN = true; } void toggleRed() { RED = !RED; } void toggleYellow() { YELLOW = !YELLOW; } void toggleGreen() { GREEN = !GREEN; } void turnLights() { if (_sp !is null) { setLED(RED, "RED"); setLED(YELLOW, "YELLOW"); setLED(GREEN, "GREEN"); } } this(ref SerialPortNonBlk sp) { _sp = sp; _cmd ~= 13; _sp.write(_cmd); _cmd = []; _cmd = cast(ubyte[]) INITIAL_SETUP_CMD ~ 13; _sp.write(_cmd); _cmd = []; } } enum string PORT = `/dev/ttyUSB0`; enum SPEED = 115_200; void main(string[] arguments) { if (arguments.length < 2) { writeln(`Usage: trlcli "<cmd>"`); } else { auto sp = new SerialPortNonBlk(PORT, SPEED); auto tlc = new TrafficLightControl!(12, 13, 5)(sp); auto shcmd = cast(char[]) arguments[1..$].join(" "); with (tlc) { lightsOff; toggleYellow; turnLights; try { auto rcmd = executeShell(shcmd); toggleYellow; if (rcmd.status != 0) { toggleRed; } else { toggleGreen; } turnLights; } catch (Throwable e) { toggleYellow; toggleRed; turnLights; writeln("Invalid process or output"); } } } }
Что здесь происходит ?
Всю работу здесь выполняет класс TrafficLightControl (дословно, управление светофором), который обладает тремя шаблонными параметрами, которые обозначают номера контактов, к которым подключены аноды (т.е положительные контакты) красного, желтого и зеленого светодиодов. Сам класс имеет внутри себя три логические переменные, которые отвечают за состояние светодиодов, а также набор методов, с помощью которых осуществляется переключение светодиодов. Самые простые методы — lightsOff (все светодиоды погашены) и lightsOn (все светодиоды включены), из которых в скрипте используется только lightsOff в самом начале процедуры работы, а комплементарный ему метод оставлен для самосогласованности, а также отладки. Остальные методы — toggleRed, toggleYellow и toggleGreen лишь переключают соответствующий светодиод в противоположное состояние. Такая схема позволяет не создавать два отдельных метода для включения/выключения светодиодов, а позволяет более согласованно ими управлять. Также, рассмотренные методы всего лишь переключают внутренние приватные переменные, а это означает, что для обновления состояния светодиода, после цепочки методов переключения следует запустить метод turnLights, который используя состояние переменных создаст ряд команд на MicroPython и отправит их по последовательному порту, что приведет к визуальной смене состояния светодиодов.
Для того, чтобы объяснить вам, как формируется код на MicroPython и что он делает, обратимся для начала к конструктору класса, который в себе содержит «процедуру начальной инициализации»/ эта процедура по сути и создает возможность коммуникации между программой на D и ESP32. В конструктор класса передается экземпляр заранее подготовленного COM-порта (по умолчанию для MicroPython, это порт с именем /dev/ttyUSB0 и скоростью передачи 115200 бод) и потом в классе подготавливается байтовый массив, через который формируются команды, который байтовым потоком пересылаются в последовательный порт. Первая такая команда — это просто один байт с кодом 13, что соответствует нажатию кнопки Enter, и эта команда готовит интерпретатор к приему последующих команд в виде обычного текста. Далее байтовый массив очищается и в него прописывается заранее подготовленный код на MicroPython, который выглядит примерно так:
from machine import Pin RED = Pin(%d, Pin.OUT) YELLOW = Pin(%d, Pin.OUT) GREEN = Pin(%d, Pin.OUT)
Данный фрагмент кода извлекает из стандартной библиотеки код для обращения к пинам ESP32, а также создает наименования переменных, которые присваиваются выбранным портам (т.е тем номерам которые были переданы как шаблонные параметры), после чего портам присваивается направление (т.е все порты будут действовать как выводы, о чем и говорит их режим Pin.OUT) и далее эти переменные (называются RED, YELLOW и GREEN) можно использовать для управления портами. Поскольку в каждую такую переменную помещен объект типа Pin, у которого есть методы on() и off(), которые включают и выключают порт, то мы их можем использовать для включения и выключения подсоединенных к этим портам светодиодов.
Рассмотренная выше процедура инициализации и передается как команда, которая заканчивается символом Enter, и именно после этого момента, светофор готов к приему команд.
Вернемся теперь к методу turnsLight, который в случае, если порт был инициализирован, осуществляет формирование команды для каждого из светодиодов на основании текущего содержимого соответствующей логической переменной, которая вместе с наименованием светодиода (оно совпадает с именем переменной для пина светодиода в MicroPython) передается в функцию setLED. Эта функция формирует текстовое представление команды для светодиода в формате MicroPython, используя упомянутые методы on/off и комбинируя их с именами соответствующего пина, после чего осуществляется перекодирование текстовой команды в поток байтов и отправка их в последовательный порт.
Остальная механика программы довольно элементарна: мы проверяем с каким количеством аргументов был вызван наш скрипт, и если оно меньше двух (это соответствует тому что скрипт вызвали без каких-либо аргументов, что неправильно), то будет выведена информация об использовании (правильном вызове) скрипта. Если же скрипт вызван правильно, то мы предполагаем, что в качестве аргумента была подана некая команда, на основании результатов которой мы и будем зажигать огни светофора. Сама команда в таком случае будет списком строк, но нам нужен для этой цели массив символов, поэтому собираем массив строк в строку и преобразуем в массив символов, а перед этим готовим порт (имя порта и его скорость заданы как значения по умолчанию в соответствующих enum) и передаем его в класс светофора, для которого указываем пины светодиодов. После этого, гасим все огни светофора и включаем желтый сигнал, как знак того, что команда, полученная из аргументов скрипта, была запущена, после чего запускаем саму команду и ловим ее код статуса. Если статус равен нулю, то это значит что команда отработала верно, и поэтому дальше выключаем желтый сигнал и зажигаем зеленый; в противном случае — гасим желтый сигнал и зажигаем красный. Также если не удалось почему-то запустить команду то, также выводим красный сигнал, погасив перед этим желтый и выводим в консоль сообщение об ошибке самого процесса.
Вот и все, и к примеру, так у одного из авторов выглядит запуск команды cargo build —release с помощью нашего скрипта (который мы назвали trlcli, сокращенно от Traffic Light CLI):

Зачем это надо ?
Ответ простой — для красоты и удобства, согласитесь, достаточно удобно запустить какую-нибудь долгосрочную сборку (привет, Rust !), выключить монитор или отправить его в ждущий режим, и потом не включая монитор, можно издали увидеть на каком-этапе сейчас идет процесс. Кроме того, светофор можно применять не только для команд сборки, а вообще для любых команд, которые могут дать в ходе своей работы код возврата, соответствующий негласной конвенции о том, что нулевой код возврата — это код успешного завершения команды.
Как и всегда напоминаем, что данный скрипт является учебным примером и лишь прототипом, и вы всячески можете его улучшать и корректировать, на этом все, большое спасибо за прочтение данной статьи.
P.S: Одному из авторов статьи всегда хотелось что-то подобное заиметь себе, но… ресурсов нет, средств нет, сплошной ремонт, да и кроме того, мы не мастера на все руки и поэтому делаем как умеем, поэтому, пожалуйста, отнеситесь с пониманием к нашим экспериментам.