Ampelmännchen — популярный символ Восточной Германии, который отображается на пешеходных светофорах на каждом углу улицы. В Берлине даже есть розничная сеть, вдохновленная их дизайном.
На барахолке я наткнулся на подержанные сигналы Ampelmann и захотел управлять ими со своего телефона. Если вы хотите сделать то же самое, читайте дальше!
Предупреждение о вреде для здоровья: в этом проекте используется питание от сети 220 В. Я не электрик. Следуйте этим инструкциям на свой страх и риск.
Я хотел построить что-то в берлинском стиле , с которым было бы интересно общаться посетителям моего дома. К сожалению, число наших посетителей в этом году сильно сократилось, но мы все еще надеемся на хорошую реакцию в 2021 году...?
Каждое устройство в вашей локальной сети имеет свой собственный IP-адрес (например, 192.168.1.20
). Некоторые маршрутизаторы, такие как FritzBox, также позволяют просматривать локальные имена хостов, так что 192.168.1.20
также доступен по адресу mydevice.fritz.box
. Для светофора имя хоста устройства — traffic-light
, поэтому мы можем посетить его по адресу http://traffic-light.fritz.box
.
Веб-приложение — это очень простое адаптивное одностраничное приложение. Он показывает:
Код находится в каталоге /webapp. Никаких внешних зависимостей не требуется, поскольку он опирается только на стандартные функции браузера, такие как преобразование CSS, WebSockets и XHR. Вы можете просмотреть работающее приложение здесь, хотя оно ничем не будет управлять, поскольку, конечно, вы не в моей локальной сети. Если вы зашли на него из локальной сети, например по адресу http://traffic-light.fritz.box
он будет работать полноценно.
После загрузки страницы приложение делает запрос GET, чтобы найти текущий статус в /api/status
, а затем открывает соединение WebSocket с сервером через порт 81
. Последующие обновления статуса всегда будут осуществляться через WebSocket, чтобы обеспечить синхронизацию нескольких клиентов. Каждый раз, когда приходит событие веб-сокета, мы применяем изменения к одному глобальному объекту state
. Вскоре после этого метод updateScreen()
применяет эти изменения к DOM.
При запуске мы также определяем, использует ли пользователь мобильное или настольное устройство, чтобы обрабатывать либо события касания, либо события щелчка. На самом деле мы используем событие touchend
для отправки команд на сервер, потому что на iPhone X это работало более надежно. Проведение пальцем вверх от нижней части экрана для выхода из Safari запускало событие touchstart
, что делало невозможным выход из приложения без включения зеленый свет!
Наконец, мы хотим снизить нагрузку на сервер везде, где это возможно. Помните, что ESP8266 работает на процессоре с частотой 80 МГц и имеет всего около 50 КБ оперативной памяти. Это НЕ мощное устройство. Поэтому, когда браузер неактивен, мы отключаем веб-сокет. Когда вкладка или браузер снова открывается, мы снова проверяем статус и повторно подключаем WebSocket.
ESP8266 занят обработкой запросов API и кода синхронизации, поэтому у него нет необходимых ресурсов для обслуживания самого веб-приложения. Кроме того, сложно вносить косметические изменения в веб-приложение, если мне нужно физически подключаться к оборудованию каждый раз, когда я хочу применить обновление.
Файл index.html веб-приложения следует принципу одностраничного приложения, согласно которому все должно отображаться с помощью Javascript, что делает сам HTML-контент очень маленьким. Вроде 550 байт мало. Все остальное загружается браузером клиента без необходимости дальнейших обращений к серверу. Таким образом, веб-приложение фактически полностью размещается на GitHub Pages, бесплатном инструменте для размещения статических сайтов. Нажатие на /index.html
фактически отправляет прокси-запрос к страницам GitHub и возвращает результат клиентскому браузеру.
Теперь мы можем изменить что угодно в веб-приложении, и это не повлияет на сервер. Большой! Ну почти...
Большая часть кода этого веб-приложения находится в файлах CSS и JS, а не в самом index.html
. Браузеры кэшируют любые загруженные файлы на неопределенный период времени, прежде чем повторно запросить их. Если index.html не изменится, но мы развернули новую версию JS, как наши клиенты узнают, что им нужно загрузить новую версию JS?
Когда мы помещаем любую новую версию нашего кода в ветку git master
, запускается действие GitHub, которое выполняет развертывание на страницах GitHub, где страница фактически становится общедоступной. Хитрость здесь заключается в добавлении суффикса ?version=latest
в конец наших собственных файлов CSS и JS в index.html
. Прежде чем копировать содержимое в ветку gh-pages
, действие использует команду sed
для замены этого « latest
» значения переменной $GITHUB_SHA
, которая на самом деле является идентификатором последнего коммита в master
ветке. (например, значение типа b43200422c4f5da6dd70676456737e5af46cb825
).
Тогда в следующий раз, когда клиент посетит веб-приложение, браузер увидит новое, другое значение после ?version=
и запросит новый, обновленный файл JS или CSS, который он еще не кэшировал.
См. метод setup(void)
в файле traffic-light-controller.ino
и раздел кода Arduino, чтобы узнать, как это работает на практике.
Я решил использовать REST и WebSockets одновременно. REST в основном используется клиентами для управления сервером. WebSockets используются для передачи информации о состоянии клиентам. Существует множество инструментов, таких как Postman, которые позволяют легко экспериментировать с REST API, поэтому я нашел это более удобным.
HTTP API: обратитесь к документации Swagger здесь.
WebSocket API: соединение с веб-сокетом отправляет большие двоичные объекты JSON, которые веб-приложение использует для обновления своего внутреннего состояния. Событие веб-сокета может содержать одно или несколько полей для обновления. Пример, содержащий информацию об окружающей среде, может выглядеть так:
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
Никакие данные в данный момент не передаются от клиента на сервер через веб-сокет, хотя это возможно.
Весь код Arduino находится в одном файле с пояснительными комментариями.
Он начинается с набора определений расположения контактов, импорта библиотек и жестко закодированных значений для таких вещей, как типы контента HTTP и значения кода ответа. Далее следует набор переменных, которые могут меняться во время выполнения, и все они имеют префикс подчеркивания. Здесь также инициализируется несколько объектов, в том числе объекты для веб-сервера, сервера веб-сокетов, клиента Wi-Fi и датчиков температуры. «Системные часы» поддерживаются полем _currentMillis
.
После загрузки запускается метод setup(void)
. После некоторой настройки контактов он создает необходимые сопоставления для конечных точек REST и запускает серверы, прослушивающие запросы клиентов. За все остальное отвечает метод loop(void)
. В каждом цикле он обрабатывает все ожидающие веб-запросы, обновляет ритм-цикл и при необходимости считывает показания датчиков. Если мы находимся в режиме вечеринки, он установит текущее состояние вспышки/импульса.
Ритм (для режима вечеринки) жестко запрограммирован для воспроизведения последовательности в поле RHYTHM_PATTERN
, но теоретически его можно изменить во время выполнения на что угодно другое. Каждый раз, когда мы вызываем метод rhythm()
, мы используем текущие значения _bpm
и _currentMillis
чтобы определить, в какой позиции мы должны находиться в паттерне. Это хранится в поле _rhythmStep
.
Во время ритмического паттерна бывают периоды, когда оба реле фактически отключаются. Но поскольку в светильниках используются лампы накаливания, они не включаются и не прекращают излучать свет мгновенно. Похоже, что лампочкам требуется около 1,7 секунды, чтобы полностью включиться или выключиться. Таким образом, добавив в схему период, когда обе лампы выключены, мы получим плавную пульсацию, когда лампы нагреваются и остывают.
В методе partyFlash()
мы получаем элемент шаблона, который должен отображаться в данный момент (или оба должны быть отключены), и вызываем lightSwitch(...)
с соответствующими параметрами. lightSwitch(...)
в свою очередь вызывает sendToWebSocketClients(...)
чтобы все подключенные клиенты обновились до нового состояния.
Если пользователь просто нажимает на один из индикаторов, чтобы включить или выключить его, процесс аналогичен, но обрабатывается как запрос REST. Вызывается один из методов handleX
, который проверяет запрос и, в свою очередь, вызывает lightSwitch(...)
.
Через более редкий интервал мы проверяем температуру двух корпусов, а также отправляем это через WebSocket всем клиентам. В настоящее время это используется только в информационных целях, но его можно использовать для отключения света, когда температура превышает определенный безопасный предел.
Благодарим @mrcosta за помощь в написании этой статьи.