Статусный светофор с D и ESP32

В этой статье мы покажем вам небольшой домашний креатив и расскажем, как своими руками собрать билд-светофор (и не только билд-) в домашних условиях с использованием почти подручных средств. Для того, чтобы повторить то, что описано будет в статье вам необходима любая отладочная плата на базе 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: Одному из авторов статьи всегда хотелось что-то подобное заиметь себе, но… ресурсов нет, средств нет, сплошной ремонт, да и кроме того, мы не мастера на все руки и поэтому делаем как умеем, поэтому, пожалуйста, отнеситесь с пониманием к нашим экспериментам.

Добавить комментарий