[Перевод] Создание e-ink дисплея с прогнозом погоды

Кому-то нравится украшать дом фоторамками, а кому-то — цифровым прогнозом погоды. Если вы из числа последних, то читайте, как собрать своё собственное метеотабло.

e7518981ae63ec97894c5e6bf0ce3e9c.jpeg

Что мы хотим узнать, выходя из квартиры? Правильно, стоит ли взять куртку потеплее? Намотать шарф, или и так сойдёт? Захватить ли зонт? Каждый раз лезть в телефон (который ещё найти надо!) не очень удобно. Уличный термометр тоже есть не у всех, да и привирает он часто.

Я решил сделать метеорологический дисплей с электронными чернилами и питанием от батареи. После изучения вопроса я определил для себя несколько целей:  

  • Наглядный прогноз погоды, который можно расположить на видном месте. Избавляет от необходимости искать телефон.

  • Выглядит как коммерческое изделие, а не детская поделка. Должен вписываться в интерьер.

  • Работает на батарейке. Не хотелось бы, чтобы из прибора торчал кабель, а замуровать его в стену не получится.

  • Текст должен быть хорошо виден, но при этом табло не должно светиться ночью. Так я пришёл к использованию электронных чернил.

  • Должен подходить, в первую очередь, для меня, но иметь возможность настройки под других. Например, опцию смены пользовательского местоположения и часового пояса. Конечный результат в итоге привязан к Финляндии из-за использования API Финского метеорологического института, но я предусмотрел и другие варианты.

Поставленные цели сопряжены с проблемами:

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

  • Низкая скорость обновления данных из-за ограничения времени работы. Мой план состоял в том, чтобы экран обновлялся один-два раза в день. Это ставит передо мной интересные дизайнерские задачи. Как показать, что данные отображаются не в реальном времени? Что следует показывать в качестве дневной температуры: среднюю или максимальную за день?

  • Физические ограничения рамки. В идеале прибор должен располагаться вровень со стеной.

План

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

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

Теперь перейдём к исполнению.

Исполнение

Сначала я разработал интерфейс в Figma. Это заняло около 2-х дней (а точнее 2 вечера после работы).

438f4b2cd7199ded0507519261aa8fd7.png

Это был дизайн для 7,5-дюймового дисплея Waveshare с электронными чернилами. После того, как я заказал дисплей, я начал искать шрифты, которые будут хорошо отображаться на 1-битном (черно-белом) экране. На удивление трудно оказалось найти настоящие 1-битные экранные шрифты, поэтому я протестировал несколько вариантов с обычными веб-шрифтами с постобработкой и без неё:

9fbe509a5d6df89f61e0a2135d004ee4.png4c7a47b25ed472544b1c07c20d34710a.png2a54266e9ad3ef77182b172a77ff55a2.png

Меня они не слишком устроили… Устройство будет находиться в самом сердце квартиры, поэтому экран должен выглядеть красиво.

Немного подумав, я дополнительно заказал 10,3-дюймовый 16-битный дисплей, который позволяет сглаживать текст. Контуры букв стали намного более плавными. Можно было жить и с 1-битным дисплеем, но я решил использовать его для чего-нибудь другого в будущем.

В тот же день я также заказал кучу другого оборудования: SD-карту, чип и аккумулятор PiJuice, стопорные винты, кабели GPIO и адаптеры. К счастью, у моего друга был запасной Raspberry PI Zero, которым он со мной поделился.

После того, как дизайн в Figma был готов, я начал создавать HTML-страницу, которая будет использоваться для визуализации пользовательского интерфейса. Многие существующие проекты используют библиотеки более низкого уровня вроде Python Imaging Library, но я выбрал HTML и CSS. Разработка идёт быстрее, когда можно редактировать HTML и сразу видеть результат без рендеринга растрового изображения после каждой итерации.

Разработка UI заняла примерно 1–2 недели, с учётом того, что я работал только по вечерам и выходным.

cfa6f23f736fa3b7c12a7525d2d028dd.jpeg

Детали по настройке вы найдёте в репозитории GitHub (перевод в конце статьи).

Когда я закончил разработку, как раз прибыло оборудование. Наконец-то я смог увидеть, как выглядит интерфейс не на моем экране с разрешением 1872×1404, а в бою. Установка и настройка Raspberry PI заняла довольно много времени, но это стоило того. Удивительно было видеть, как интерфейс впервые появляется на e-ink дисплее.

Ночные бденияНочные бдения

До этого момента всё шло на удивление гладко. Но потом я собрал все части воедино и понял, что рамка безбожно мала. 

9eb83f40048fe251c58eb96d13601df0.jpeg

Мне очень не хотелось делать толстый корпус, поэтому пришлось вернуться к чертежам. Я пытался уместить всё в рамке, тестируя всевозможные комбинации. Этот чудовищный кабель GPIO + переходник ½ почти справились со своей задачей:

8b9d538d66627ac35f5239fd2ffe6ea2.jpeg

Но даже после того, как я срезал все лишние пластиковые куски с кабелей, установить раму заподлицо со стеной не удалось.

Затем я понял, что на демонстрационной странице Waveshare есть приложение для Windows, которое подключается к контроллеру через интерфейс micro-USB. Можно ли использовать его для управления дисплеем? Не было никаких примеров того, как это сделать в Linux.

К счастью, некоторые великие умы уже задумались об этом. Я подключил контроллер с помощью USB-кабеля, скомпилировал код C на Raspberry PI и в качестве теста попытался очистить дисплей. Сработало! Какое облегчение. Кабели USB намного меньше, чем 40-контактные кабели и разъёмы GPIO.

Теперь, когда я был уверен в USB-стратегии, мне нужно было избавиться от разъёма GPIO контроллера IT851, потому что он всё ещё не влезал в рамку. К счастью, в Хельсинки есть замечательная библиотека Oodi (хакерское пространство, которое представляет собой смесь библиотеки, игрового кафе и места для встреч), куда я мог пойти и бесплатно отпаять разъём.

Фото сделано в той самой библиотеке Oodi, в комнате электроники, где есть классное оборудование, например, лазерный резак, 3D-принтер и многое другоеФото сделано в той самой библиотеке Oodi, в комнате электроники, где есть классное оборудование, например, лазерный резак, 3D-принтер и многое другое

Уму непостижимо, насколько крут Oodi не только в плане архитектуры, но и с точки зрения функциональности.

К сожалению, паяю я не блестяще. Жене сказал, что работа займёт около 20 минут, но даже спустя 1,5 часа работы (и активного гугления) я не смог удалить разъем с чипа. Моё время в паяльной лаборатории истекло.

Следующей стратегией было применение грубой силы. Я взял бокорезы и просто отрезал половину жатки. Единственное, на что я мог надеяться, это на то, что после моего акта вандализма чип заработает. С разъёмом GPIO, разрезанным пополам, все детали, наконец, прекрасно влезли в рамку от IKEA!

Непонятные проблемы в программном обеспечении были решены, и можно было начинать окончательную сборку. Я прикрепил все к рамке ИКЕА с помощью винтов и термоклея. Дисплей очень тонкий, а задняя крышка слегка прогибается, поэтому я разместил винты так, чтобы они не повредили дисплей при переноске устройства.

78ff2ca5739e7e17f3c5ce9b5aad249c.jpeg

Та-дам! Все составные части почти идеально вписались в рамку ИКЕА толщиной 2,9 см. 

865a9025906bd4017c73bc3fc4b710b3.jpeg

Я очень беспокоился, что повредил компоненты в процессе целого часа непрофессиональной распайки, но, к счастью, всё работало.

Наконец, я протестировал всё на ошибки.

ad0dfc780e239892747d154805dbce98.jpega490c0db572395fc958d07342d44f24f.jpeg

Готово, пришло время крепить раму к стене.

0bc08199993c078ab4ab32a8849c30d5.jpegfc1ca40852f639cc0bbe20e2f10ed2b5.jpeg3b1535d0a5539e36d4b6775e95cfc197.jpeg

Мне очень нравится итоговый результат. Рамка ИКЕА имеет белую картонную вставку, которую я обрезал по размеру экрана. Чтобы не было несоответствия пропорций вставки и самой рамки, я пожертвовал несколькими вертикальными пикселями e-ink дисплея. Отверстие вставки по вертикали короче, чем у дисплея с электронными чернилами, поэтому некоторые Y-пиксели остаются неиспользованными. Немного стыдно, но общий вид важнее всего!

Один из вариантов, который я рассматривал — распилить рамку таким образом, чтобы она идеально подходила под экран. Но получится фигня, к тому же дополнительное пространство внутри позволило мне уместить там все компоненты.

Проект занял 3 недели. Даже удивительно, что так быстро. Я намеренно выбрал знакомое оборудование и программное обеспечение, чтобы упростить разработку. Идея состояла в том, чтобы действительно закончить проект, хотя и покопаться в нём было полезно и интересно. 

Вот и все, было весело!

Список оборудования для справки

  • Raspberry PI Zero W

  • PiJuice Zero

  • Аккумулятор PiJuice 12000 мАч. Как можно большей ёмкости, чтобы не приходилось часто заряжать устройство.

  • 10,3-дюймовый дисплей Waveshare с электронными чернилами и разрешением 1872×1404 с Raspberry PI HAT. Поддерживает 16 оттенков черного и белого.

  • Инструмент для установки Geekworm Raspberry Pi, 132 шт. Для набора прокладок и винтов, которые прекрасно подходят для проектов Raspberry PI.

  • Переходник с micro-USB на USB

  • Кабель micro-USB на USB

  • Рамка IKEA Hovsta Frame

  • Разные строительные штуки: термоклей, резинка для волос, чтобы держать аккумулятор, настенные монтажные крючки, небольшая пластиковая коробка, разрезанная на части, чтобы поддерживать аккумулятор снизу, и, конечно же, клейкая лента.

Из репозитория GIT

Проект состоит из двух составляющих: render и rasp.

render

На этом этапе создаётся HTML, который в конечном итоге будет отображаться как PNG. Изображение содержит прогноз погоды. Render предоставляется через Google Cloud Function. Это идеальный инструмент для такого рода задач. Эндпоинт вызывается редко, а задержки не имеют большого значения.

  • Данные о погоде извлекаются из API-интерфейсов Финского метеорологического института (ФМИ) и Open Meteo. API ФМИ имел некоторые ограничения, поэтому я дополнил данные информацией с Open Meteo. 

  • HTML, CSS и Headless Chrome используются для создания файла PNG. Эту часть можно было бы реализовать с помощью низкоуровневого подхода, но использовать для разметки CSS очень удобно.

  • Отображается вcё одним примитивным HTML-файлом, который содержит фиктивные данные для упрощения разработки. Фиктивные данные будут заменены реальными с использованием идентификаторов DOM. Отсутствие инструмента сборки устраняет множество ненужных сложностей.

  • Все даты в системе указаны в формате UTC, при рендеринге они конвертируются в местное время. 

rasp

Работает на Raspberry Pi Zero.

Сюда входит весь код, относящийся к оборудованию, которое будет отображать погоду. Эта часть ничего не знает о погоде, она просто загружает PNG с заданного URL-адреса и отображает его на дисплее e-Ink.

  • Извлекается PNG из заданного URL-адреса, он отображается на дисплее e-Ink и происходит возврат в режим ожидания. 

  • Должно потребляться как можно меньше энергии

  • Могло бы хватить микроконтроллера, но мне хотелось бы закончить проект до конца своей жизни

  • Код IT8951-ePaper скопирован с https://github.com/waveshare/IT8951-ePaper/

Монтаж

Примечание: это примерное руководство, написанное по памяти.

Большая часть программного обеспечения находится в облаке Google. Это снимает большую часть нагрузки с устройства Raspberry Pi, что позволяет увеличить время автономной работы. Развёртывание выполняется с помощью GitHub Actions, но первоначальная настройка была примерно такой:

  • Создать новый проект GCP

  • Создайте учётную запись службы развёртывания с ролью развёртывания Cloud Function. Установите ключ JSON как GCP_SERVICE_ACCOUNT_KEY secret.

  • Создайте ещё одну учётную запись службы для устройства Raspberry PI. Добавьте права записи для Cloud Logging. Таким образом, журналы могут быть отправлены в GCP для отладки, потому что в режиме сна Raspberry PI не использует батарею питания.

  • Создайте облачную функцию Google с исходным кодом hello world. К переменным окружения добавьте NODE_ENV=production и API_KEY=. Ключ API предназначен только для предотвращения случайных вызовов http, потребляющих квоты GCP. Рендеринг Headless Chrome, кажется, хорошо работает с 1 ГБ памяти.

Настройка Raspberry PI

  • Загрузите правильный образ отсюда: https://www.raspberrypi.com/software/operating-systems/b

  • Залейте его на SD-карту с помощью balenaEtcher https://www.balena.io/etcher/ (или другой программы для записи образов)

  • Загрузите Raspberry и выполните первоначальную настройку

  • sudo raspi-config

    • Настройка SSID и пароля Wi-Fi (параметры системы)

    • Обновление языка системы, часовых поясов и т. д. (параметры локализации)

    • Включение SSH-сервера (параметры интерфейса)

    • Включение overlayfs (параметры производительности), чтобы сделать FS доступной только для чтения.

  • В вашем маршрутизаторе обязательно назначьте статический локальный IP-адрес для устройства.

  • Установите код обновления дисплея
    Скачайте архив

curl -H "Authorization: token " -L https://api.github.com/repos/kimmobrunfeldt/eink-weather-display/zipball/main > main.zip

или sudo apt install git и

git clone https://:@github.com/kimmobrunfeldt/eink-weather-display.git

Вы можете создать ограниченный токен личного доступа Github, который может клонировать это repo. Git мне показался самым простым, можно просто сделать git pull для внесения любых изменений.

  • sudo apt install python3-pip

  • Мне не удалось заставить pipenv работать благодаря тому, что pijuice является общесистемным пакетом. 

  • cd eink-weather-display && pip install Pillow==9.3.0 google-cloud-logging requests python-dotenv

Установка пакетов Python

  • Настройте переменные env: cp .env.example .env и заполните детали

  • Следуйте инструкциям по установке с https://www.waveshare.com/wiki/10.3inch_e-Paper_HAT, чтобы заставить дисплей E-Ink работать.

  • После установки проверьте, работает ли демонстрационное программное обеспечение (на языке C).

  • sudo apt install pijuice-base

  • Включите интерфейс I2C
    Дополнительная информация об отладке:

  • Чтобы разрешить PIJuice включаться без питания от батареи, перейдите в общие настройки и включите «Turn on without battery» или аналогичную опцию.

  • Убедитесь, что вы используете правильный профиль батареи PIJuice (у меня PJLIPO_12000)
    Если используете pijuice_cli, не забудьте применить изменения! Кнопка запрятана внизу.

  • Проверьте, работает ли PIJuice и от батареи.

  • cd rasp/IT8951 и следуйте инструкциям по установке

  • Настройте Pijuice с использованием pijuice_cli. Не забудьте сохранить изменения внутри каждого экрана настройки!

    Будильник: каждый день в 04:00 UTC (6:00 по хельсинкскому времени зимой, 7:00 летом)

  • После этого вы можете протестировать дисплей PiJuice + E-Ink.

  • Настройте crontab. Запустите обновление при загрузке и выключите устройство, если оно работает от батареи.

    @reboot cd /home/pi/eink-weather-display/rasp && python main.py

    # Every minute

    * * * * * cd /home/pi/eink-weather-display/rasp && python shutdown_if_on_battery.py

Примечание: Всё, что было сделано выше, я делал с использованием пинов Raspberry PI GPIO. Однако они оказались слишком высокими для рамы. Вместо того, чтобы перепаивать пины GPIO, я решил использовать контроллер IT8951 через USB-интерфейс.

  • Установите https://git.sr.ht/~martijnbraam/it8951 в каталог /home/pi/usb-it8591 и скомпилируйте его в Raspberry.

    • Найдите, какой /dev/sdX является вашим USB-устройством, и измените все команды main.py соответственно

  • sudo apt install imagemagick

  • Отредактируйте main.py, указав правильные отступы. Из-за особенностей конструкции видны не все пиксели дисплея E-Ink. 

Разработка с данными-заполнителями

  • npm i

  • npm start

  • Откройте http://127.0.0.1:8080/ , чтобы настроить визуальные элементы со значениями заполнителей, жёстко запрограммированными в src/templates/index.html.

Отображение реальных значений

  • Откройте http://127.0.0.1:8080/render.html.

  • npm run render для запуска инструмента CLI, который отображает HTML вsrc/templates/render.html

Вызов облачной функции

Облачная функция и интерфейс командной строки поддерживают основные операции с изображениями, чтобы выгрузить эту работу из Raspberry: rotate, flip, flip, padding (Top|Right|Bottom|Left), resizeToWidth, resizeToHeight. См. Sharp для их документов. Например --flip с CLI или ? flip=true с CF.

LAT=»60.222»

LON=»24.83»

LOCATION=«Espoo»

BATTERY=»100»

TIMEZONE=«Europe/Helsinki»

curl -vv -o weather.png \

  -H «x-api-key: $API_KEY» \

  «https://europe-west3-weather-display-367406.cloudfunctions.net/weather-display? lat=$LAT&lon=$LON&locationName=$LOCATION&batteryLevel=$BATTERY&timezone=$TIMEZONE»

© Habrahabr.ru