Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI

Статей о создании метеостанции на базе Arduino не счесть. Можно сказать, если статья про метеостанцию, то это про микроконтроллеры Arduino, ESP32 или STM32. Но только не в этот раз. Будем запускать метеостанцию на Banana Pi BPI-M64 под Linux, без использования Arduino-подобных оберток в виде WiringPi, на C# .NET5. Пример метеостанции является демонстрацией встраиваемого решения работы с GPIO, датчиками и вывода пользовательского интерфейса напрямую на LCD. В решении используется: Linux (Armbian) — основная ОС, .NET и C# — платформа для создания прикладного ПО, AvaloniaUI — графической интерфейс с интерактивными графиками и анимацией, Docker — инструмент для развертывания, управления, доставки приложений, RabbitMQ — брокер сообщений для передачи сообщений между контейнерами. Благодаря использованию универсального подхода и технологии Docker, приложение можно запустить не только на Banana Pi BPI-M64, но и на других Banana/Orange/Rock/Nano Pi одноплатных компьютерах, включая Raspberry Pi.

О графическом интерфейсе


Особенностью предложенного решения является использование кроссплатформенного фреймворка AvaloniaUI с передачей Docker-контейнеру виртуального устройства Linux framebuffer. Совершенно другой подход к построению UI был рассмотрен на Хабре в статье «Интерфейсы для встраиваемых устройств на современных Web-технологиях», проще говоря, интерфейс на HTML в браузере. Какие особенности присутствуют в обоих предложенных вариантах, а также их достоинства и недостатки рассмотрим в конце данного поста.

Постановка задачи


Нам предстоит считывать показания датчиков, затем отправлять полученные данные брокеру сообщений RabbitMQ. Где второе приложение заберет полученные данные из очереди RabbitMQ и выведет их на LCD-экран. RabbitMQ позволяет организовать слабосвязанную архитектуру. Если в дальнейшем потребуется отправлять данные в облако и/или сохранять на диске, то, не меняя существующие приложения, потребуется лишь добавить еще одно приложение, которое реализует новую функциональность.

Принцип работы


После старта ОС Armbian на Banana Pi BPI-M64, запускается Docker-демон, который в свою очередь запускает контейнеры. Графический интерфейс выводится на LCD ILI9341. Доступно два состояния экрана. На первом экране отображается текущая температура, влажность, давление. На втором экране отображается график изменения температуры с автоматическим обновлением. Пользователь может взаимодействовать с системой посредством кнопки. Нажатие на кнопку приводит к включению светодиода для индикации текущего действия. При отпускании кнопки светодиод выключается и отправляется команда смены экрана. На экране отображения датчиков присутствует иконка индикации состояния подключения к серверу RabbitMQ. Когда установлено соединение с сервером RabbitMQ, иконка — зеленого цвета; соединение отсутствует — иконка  красного цвета.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Отображение физических величин на LCD ILI9341

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Отображение графика изменения температуры

Запуск метеостанции на Banana Pi M64 (сокращенный вариант):


Архитектура


Решение работает на одноплатном компьютере Banana Pi BPI-M64, ОС Armbian 21.02.1 (Ubuntu 18.04.5 LTS Bionic Beaver, ядро Linux 5.10.12). Приложения развертываются в виде Docker-контейнеров. Поэтому в случае развертывания приложений метеостанции на других одноплатных компьютерах, потребуется поддержка Docker. Технология Docker позволяет существенно упростить развертывание и доставку приложений на устройство. Одно из самых главных качеств Docker является его атомарность. Контейнер целиком либо развернется, либо нет. В случае сбоя запуска нового контейнера можно применить систему A/B, при которой контейнер предыдущей версии не удаляется, а происходит откат при невозможности запуска нового контейнера. Подобная манипуляция выполняется быстро без задержек, что снижает время простоя устройства. При традиционном подходе пошагового обновления, в случае сбоя в процессе обновления, откат изменений становится весьма сложной задачей. Европейская компания Toradex в своих встраиваемых решениях для автомобильных грузоперевозок для доставки и развертывания приложений использует технологию Docker. Компанией был разработан модуль Verdin на базе NXP i.MX8 M Mini SoC, который взаимодействует с автомобилем через CAN-шину для получения телеметрии от автомобиля и отправляет эти данные в облако. Работа с CAN-шиной и отправка данных в облако выполняется в Docker-контейнере.

Docker ARM ARM64 Banana Pi BPI-M64 Cubietruck

Подробнее почитать в публикации Reading Vehicle OBD-II data through CAN within a containerized application in Embedded Linux — CNX SOFTWARE. Установить Docker на ARM и 64-bit ARM можно по руководству Install Docker Engine on Ubuntu.

Данные между контейнерами передаются через брокера сообщений RabbitMQ по протоколу AMQP. Протокол AMQP (Advanced Message Queuing Protocol) — позволяет гибко связывать  отдельные подсистемы (или независимые приложения) между собой. AMQP-брокер осуществляет маршрутизацию, возможно, гарантирует доставку, распределение потоков данных, подписку на нужные типы сообщений. Например, на устройстве работает два приложения, первое собирает телеметрию, а второе отправляет данные в облако. Если второе приложение по каким-либо причинам аварийно завершится, то при использовании брокера сообщений данные телеметрии не потеряются, а будут накапливаться в очереди брокера сообщений. После успешного запуска второго приложения данные из очереди будут прочитаны и отправлены в облако. В случае разработки системы без использования брокера сообщений разработчикам потребовалось бы отдельно решать задачу накопления и хранения данных в случае невозможности их отправки в облако. Поэтому брокер сообщений является универсальным решением подобной задачи и избавляет разработчиков от лишней заботы по сохранению данных. Универсальность протокола AMQP позволяет серверу RabbitMQ взаимодействовать с самыми различными устройствами, включая микроконтроллеры и различные типы клиентских приложений.
Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI

Взаимодействие с сервером RabbitMQ

Для замера физических величин температуры, влажности и давления используется датчик BME280, подключается к шине I2C. В связи с торговой войной между США и Китаем некоторые позиции датчиков резко взлетели в цене, поэтому в качестве альтернативы можно использовать  датчик DS18B20, работающий по протоколу OneWire. Поэтому на борту вашего одноплатного компьютера должны быть контакты GPIO и/или шина I2C в случае подключения BME280. Изображение выводится через виртуальное устройство Linux  Framebuffer. Содержимое framebuffer обычно напрямую отображается на доступном экране. В качестве экрана может использоваться HDMI-монитор или LCD на SPI интерфейсе. В данном случае используется дисплей SPI LCD ILI9341, под его разрешение сделана разметка.

Большая схема, как все это работает:
Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI

Архитектура метеостанции на Banana Pi M64

Далее будет рассмотрено, как все это адаптировать под свой одноплатный компьютер. Некоторые технические моменты в статье пропущены, т.к. статья основывается на публикации Управляем контактами GPIO из C# .NET 5 в Linux на одноплатном компьютере Banana Pi M64 (ARM64) и Cubietruck (ARM32). Поэтому желательно в указанной публикации ознакомиться со следующими понятиями: что такое GPIO, библиотека Libgpiod, как вычисляются номера контактов GPIOXX, библиотеки .NET IoT, принцип работы со светодиодом и обработки событий от кнопки. Раздел установки библиотеки Libgpiod и .NET можно пропустить, т.к. Docker-контейнер уже будет содержать библиотеку и среду исполнения.

Аппаратное обеспечение


На плате Banana Pi BPI-M64 размещен SoC Allwinner A64, в него входит 4 ядра Cortex-A53 с частотой 1.2 ГГц, с 2 ГБ DDR3. Для хранения данных используется карта micro SD Class 10 объемом 16 Гб. На уровне ОС требуется поддержка Docker CE. На плате должны быть контакты GPIO для подключения датчиков, кнопки и светодиода. Минимальное требование — это возможность подключения температурного датчика DS18B20 по OneWire-протоколу. К 40-контактному совместимому с Raspberry Pi разъему подключаются:

  • BME280 — датчик температуры, влажности и давления;
  • DS18B20 — альтернативный датчик температуры, в случае отсутствия BME280;
  • Кнопка — для переключения экранов, наличие необязательно;
  • Светодиод — для индикации нажатия на кнопку, наличие необязательно;
  • LCD ILI9341 — экран, подключаемый к SPI-шине, можно использовать любой другой LCD на HMDI или VGA интерфейсе.

Схема подключения


Устройства для подключения:


Итоговая схема будет выглядеть следующим образом
Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI

Принципиальная схема подключения устройств к Banana Pi BPI-M64

Для включения интерфейсов I2C, OneWire, SPI необходимо на уровне ОС Linux включить соответствующие устройства. Это делается путем формирования файла наложения дерева устройств DTO. Как формируются файлы наложения устройств (DTS), можно почитать в публикации Работа с GPIO на примере Banana Pi BPI-M64. Часть 2. Device Tree overlays.

Файлы DTS для включения устройств:


Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI

Подключенные устройства к Banana Pi BPI-M64, вид сверху

Docker-контейнер с библиотекой Libgpiod


Библиотека Libgpiod позволяет работать с контактами GPIO одноплатного компьютера из .NET среды исполнения, она может быть установлена на Linux (Armbian). Не является аппаратно-зависимой, что позволяет ее использовать на различных одноплатных компьютерах архитектуры: ARM32, ARM64, и x86.

Для работы с контактами GPIO в публикации Управляем контактами GPIO из C# .NET 5 в Linux на одноплатном компьютере Banana Pi M64 (ARM64) и Cubietruck (ARM32), требовалась установка искомой библиотеки с репозитория. Сейчас создан Docker-контейнер с библиотекой, и можно запускать из контейнера утилиты: gpiodetect, gpioinfo, gpioset, gpioget, gpiomon. Образ Docker devdotnetorg/libgpiod, GitHub devdotnetorg/docker-libgpiod.

Доступ к контактам GPIO осуществляется через устройства /dev/gpiochipX. Для просмотра доступных устройств на одноплатном компьютере необходимо выполнить команду:

$ ls /dev/gpiochip*

Результат выполнения команды:

root@bananapim64:~# ls /dev/gpiochip*
/dev/gpiochip0 /dev/gpiochip1 /dev/gpiochip2

Соответственно, доступ к данным устройствам необходимо передать контейнеру devdotnetorg/libgpiod. Предоставим доступ ко всем доступным устройствам /dev/gpiochipX и выполним команду gpiodetect. Утилита gpiodetect выведет список всех чипов GPIO, их метки и количество линий. Команда будет выглядеть следующим образом:

$ docker run --rm --name test-libgpiod --device /dev/gpiochip0 --device /dev/gpiochip1 --device /dev/gpiochip2 devdotnetorg/libgpiod gpiodetect

где "--device /dev/gpiochip0" — доступ к устройству, gpiodetect — название утилиты, вызываемой из контейнера.

Результат выполнения команды:

root@bananapim64:~# docker run --rm --name test-libgpiod --device /dev/gpiochip0 --device /dev/gpiochip1 --device /dev/gpiochip2 devdotnetorg/libgpiod gpiodetect
gpiochip0 [1f02c00.pinctrl] (32 lines)
gpiochip1 [1c20800.pinctrl] (256 lines)
gpiochip2 [axp20x-gpio] (2 lines)

В публикации Управляем контактами GPIO из C# .NET 5, кнопка и светодиод располагались в устройстве  /dev/gpiochip1, поэтому достаточно предоставить доступ только к данному устройству. Повторим выполнение команды gpiodetect, но только предоставляя доступ к /dev/gpiochip1, команда:

$ docker run --rm --name test-libgpiod --device /dev/gpiochip1 devdotnetorg/libgpiod gpiodetect

Результат выполнения команды:

root@bananapim64:~# docker run --rm --name test-libgpiod --device /dev/gpiochip1 devdotnetorg/libgpiod gpiodetect
gpiochip1 [1c20800.pinctrl] (256 lines)

Команда выполнена успешно.

Теперь, как в предыдущей публикации, включим светодиод на 36 контакте, выставим значение «1», команда:

$ docker run --rm --name test-libgpiod --device /dev/gpiochip1 devdotnetorg/libgpiod gpioset 1 36=1

В результате светодиод включится.

Приложение dotnet-gpioset на C#


В пространство имен System.Device.Gpio.Drivers входит драйвер — LibGpiodDriver. Драйвер LibGpiodDriver использует библиотеку Libgpiod для получения доступа к портам GPIO, заменяет драйвер SysFsDriver. Приложение dotnet-gpioset на .NET5, используя драйвер LibGpiodDriver, выполняло ту же самую функцию, что и утилита gpioset из состава библиотеки Libgpiod. Данное приложение доступно в виде Docker-контейнера devdotnetorg/dotnet-gpioset GitHub dotnet-libgpiod-gpioset.

Включим светодиод, но уже из .NET кода, используя библиотеку Libgpiod, команда:

$ docker run --rm --name test-dotnet-gpioset --device /dev/gpiochip1 devdotnetorg/dotnet-gpioset 1 36=1

Результат выполнения команды:

root@bananapim64:~# docker run --rm --name test-dotnet-gpioset --device /dev/gpiochip1 devdotnetorg/dotnet-gpioset 1 36=1
Args gpiochip=1, pin=36, value=High
OK

В результате светодиод включится. Dockerfile данного контейнера можно использовать в качестве шаблона для других приложений, использующих драйвер LibGpiodDriver для доступа к контактам GPIO одноплатного компьютера.

Подключение датчика BME280 к шине I2C


Датчик BME280 подключается к шине I2C. I2C InterIC, или IIC (I2C) — двунаправленная шина передачи данных, разработанная еще в 1980 году компанией Philips для осуществления связи между разными схемами и устройствами. Очень часто на схемах указывают I2C/TWI(Two Wire Interface) — это одно и то же. Дело в том, что компания Philips шину I2C запатентовала. В результате для использования шины I2C другие разработчики обязаны были заплатить роялти, что конечно же не очень то хотелось. Так появился клон шины I2C — шина TWI, свободная от лицензионных отчислений. Все, что применимо к шине I2C, применимо и к шине TWI, и наоборот.

К шине I2C можно подключить до 127 устройств. Передача данных осуществляется по двум проводам:

  • SDA (Serial Data) — эта линия отвечает непосредственно за передачу данных;
  • SCL (Serial Clock) — эта линия отвечает за синхронизацию соединения.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI

Распайка шины I2C с подтягивающими резисторами сопротивления 4,7 кОм

Адресация в шине I2C


Каждое устройство, подключённое к шине, может быть программно адресовано по уникальному адресу. В обычном режиме используется 7-битная адресация.

Шина I2C/TWI на процессоре Allwinner A64


Рассмотрим спецификацию SoC Allwinner A64:

  • Два контроллера интерфейса TWI;
  • Поддержка стандартного режима (до 100 Кбит/с) и быстрого режима (до 400 Кбит/с) передачи данных;
  • Роль ведущего (master) / ведомого (slaves) настраивается;
  • Доступны транзакции с 10-битной адресацией;
  • Возможность работы в широком диапазоне входных тактовых частот.

На самой плате разведено два интерфейса TWI:

  • TWI0 — располагается на разъеме MIPI DSI display;
  • TWI1 — располагается на 40-контактном разъеме (типа Raspberry Pi 3 GPIO), к нему будем подключать датчики.

Для считывания значений с датчика с BME280 из .NET кода необходимо знать номер линии I2C. В данном случае будет использоваться интерфейс TWI1 с индексом «1». Поэтому номер линии I2C для .NET кода будет — «1».

Датчик BME280


Linux I2C Bosch BME280

Модуль BME280 фирмы Bosch Sensortec предназначен для измерения атмосферного давления, температуры и влажности. По сравнению с первыми датчиками серии (BMP085 и BMP180) он имеет лучшие характеристики и меньшие размеры. Отличие от датчика BMP280 – наличие гигрометра, что позволяет измерять относительную влажность воздуха и создавать на его основе маленькую метеостанцию.

Технические характеристики модуля BME280:

  • Интерфейс: SPI, I2C;
  • Напряжение питания: от 3,3 до 5 В;
  • Диапазон измерений давления: 300-1100hPa;
  • Диапазон измерений температуры: -40 — +85 °C;
  • Диапазон измерений влажности: 0 — 100 %;
  • Энергопотребление: режим измерений — 3.6 нА; в спящий режим: — 0.1 нА;
  • Точность измерений: давление — 0.01 hPa ( < 10 cm). Температура — 0.01° C. Влажность – 3%.

Назначение контактов:

  • VCC — питание модуля 3.3 В или 5 В;
  • GND —  Ground;
  • SCL — линия тактирования (Serial CLock);
  • SDA — линия данных (Serial Data).

Данный модуль работает по двухпроводному интерфейсу I2C, адрес по умолчанию 0x76.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Датчик BME280 на 3,3 В

Новое поколение датчиков BOSCH обладают низким энергопотреблением. Например, для сбора показаний влажности и температуры раз в секунду потребуется всего 1,8 мкА. Если нужно анализировать еще и давление, суммарный ток составит 3,6 мкА. В режиме сна датчик потребляет и вовсе 0,1 мкА.

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

Данный датчик емкостного типа, что заведомо делает его более точным, чем резистивные датчики типа DHT11.

DataSheet на датчик BME280 можно загрузить по ссылке BST-BME280_DS001-10 [PDF 1,84 МБ]

Схема подключения датчика BME280 к шине I2C


Исходя из схемы (Распиновка GPIO для Banana Pi BPI-M64), шина I2C располагается на контактах № 3 и 5. Контакт №3 — TWI1-SDA — передача данных. Контакт №5 — TWI1-SCL — синхронизация, время. На линию TWI1-SDA и TWI1-SCL установим подтягивающие резисторы сопротивлением 4,7 кОм к линии питания VCC.

Linux I2C Bosch BME280
Схема подключения датчика BME280 к шине I2C

Файл наложения устройств DTS для включения шины I2C


По умолчанию в основном дереве устройств sun50i-a64-bananapi-m64.dts есть узел для шины I2C, и он включен по умолчанию.

Узел i2c1-pins (из файла sun50i-a64-bananapi-m64.dts) по адресу "/soc/pinctrl@1c20800/i2c1-pins" содержит указания используемых контактов:

i2c1-pins {
	pins = "PH2", "PH3";
	function = "i2c1";
	bias-pull-up;
	phandle = <0x3b>;
};

Узел i2c@1c2b000 (из файла sun50i-a64-bananapi-m64.dts) по адресу "/soc/i2c@1c2b000" содержит само устройство I2C:

i2c@1c2b000 {
	compatible = "allwinner,sun6i-a31-i2c";
	reg = <0x1c2b000 0x400>;
	interrupts = <0x0 0x7 0x4>;
	clocks = <0x2 0x40>;
	resets = <0x2 0x2b>;
	pinctrl-names = "default";
	pinctrl-0 = <0x3b>;
	status = "okay";
	#address-cells = <0x1>;
	#size-cells = <0x0>;
	phandle = <0x88>;
};

Как видно из примера, статус устройства status = «okay» означает, что шина I2C включена и можно подключать датчики. Но если будет выключена, то потребуется создать файл dts для включения шины I2C. На этот случай приведен пример такого файла.

Создадим файл DTS с названием: sun50i-a64-i2c1-on.dts:

/dts-v1/;
/plugin/;

/ {
	compatible = "allwinner,sun50i-a64";

	fragment@0 {
		target-path = "/aliases";
		__overlay__ {
			i2c1 = "/soc/i2c@1c2b000";
		};
	};

	fragment@1 {
		target = <&i2c1>;
		__overlay__ {
			pinctrl-names = "default";
			pinctrl-0 = <&i2c1_pins>;
			status = "okay";					
		};
	};
};

Рассмотрим параметры:

  • pinctrl-0 = <&i2c1_pins> — ссылка на используемые контакты узла по адресу "/soc/pinctrl@1c20800/i2c1-pins".
  • status = «okay» — задействует шину I2C на плате для подключения устройств

Разместим файл по пути /boot/dtb/allwinner/overlay. Затем компилируем файл .dts в .dtbo:

$ dtc -O dtb -o sun50i-a64-i2c1-on.dtbo sun50i-a64-i2c1-on.dts

Запустим утилиту конфигурирования платы: armbian-config. Перейдем по меню: System > Hardware, включим слой (overlay): sun50i-a64-i2c1-on. После перезагрузки платы шина I2C будет включена.

Поиск устройств на шине I2C


После включения шины I2C и подключения датчика BME280 необходимо убедиться, что все работает и датчик отзывается. Для этого необходимо установить утилиту i2c-tools, которая производит поиск всех устройств, подключенных к шине I2C.

Установка утилиты i2c-tools:

sudo apt-get update
sudo apt-get install -y python-smbus
sudo apt-get install -y i2c-tools

Формат команды поиска устройств на шине I2C: sudo i2cdetect -y 0, где 0 — номер шины I2C (1,2,3,..). На 40-контактном разъеме (типа Raspberry Pi 3 GPIO) располагается TWI1, к которому подключили датчик BME280. Поэтому вызываемая команда будет выглядеть так: sudo i2cdetect -y 1. Выполним поиск устройств на шине I2C порт «1»:

root@bananapim64:~# sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
root@bananapim64:~#

Шина I2C работает, и датчик BME280 по адресу 0x76 найден. В ОС Linux считать показания с датчика  возможно через виртуальную ФС Sysfs. Подробнее про шину I2С в публикации Работа с GPIO в Linux на примере Banana Pi BPI-M64. Часть 5. Device Tree overlays. Шина I2C, подключение датчиков Bosh BMx.

Если по каким-либо причинам вызов утилиты завершается ошибкой, проверьте наличие включенных устройств I2C. В ФС должны быть файл-устройства в зависимости от номера линии I2C: /dev/i2c-0, /dev/i2c-1 и т.д. Если данного устройства нет, значит, необходимо проверить файл DTS для включения шины I2C.

Подключение датчика DS18B20


Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI

Датчик DS18B20

Этот раздел можно пропустить, если подключение датчика BME280 прошло успешно. Подключать датчик DS18B20 можно на любой доступный контакт GPIO одноплатного компьютера. Для возможности получения значений датчика из .NET кода необходимо на уровне ОС включить интерфейс 1-Wire. Как это сделать, можно узнать в разделе «Создание своего DTBO для протокола 1-Wire» в публикации Работа с GPIO на примере Banana Pi BPI-M64. Часть 2. Device Tree overlays.

Установка и настройка брокера сообщений RabbitMQ


RabbitMQ ‒ это брокер сообщений. Его основная цель ‒ принимать и отдавать сообщения. Ознакомится с RabbitMQ можно в небольшом цикле публикаций RabbitMQ. Часть 1. Introduction. Erlang, AMQP.

В этом разделе будут настройки, которые необходимо выполнить на сервере RabbitMQ. Настройка клиентов сервера RabbitMQ будет в следующих разделах.

Протокол AMQP вводит три понятия:

  • exchange (обменник или точка обмена) — принимает сообщения от поставщика. Обменник распределяет сообщение в одну или несколько очередей. Он маршрутизирует сообщения в очередь на основе созданных связей (binding) между ним и очередью;
  • queue (очередь) — структура данных, состоящая из сообщений, существует внутри RabbitMQ. Хотя сообщения проходят через RabbitMQ и приложения, хранятся они только в очередях. Очередь не имеет ограничений на количество сообщений, она может принять сколь угодно большое их количество ‒ можно считать ее бесконечным буфером. Любое количество поставщиков может отправлять сообщения в одну очередь, также любое количество подписчиков может получать сообщения из одной очереди;
  • binding (привязка) — правило, которое сообщает точке обмена, в какую из очередей эти сообщения должны попадать. Обменник и очередь могут быть связаны несколькими привязками.

Пользователи делятся на:

  • Publishers (поставщики) — клиентское приложение, отправляет сообщения;
  • Consumers (подписчики) — клиентское приложение, принимает сообщения из очереди. Обычно подписчик находится в состоянии ожидания сообщений.

При организации доступа к данным в RabbitMQ будем придерживаться принципа минимальных привилегий. В RabbitMQ будет два пользователя:

  • user-sensors: отправка сообщений на сервер, для приложения WeatherStation.Sensors;
  • user-lcd: чтение сообщений, для приложения WeatherStation.Panel.

Соответственно, пользователь user-sensors будет Поставщиком (Publisher), а user-lcdПодписчиком (Consumer).

Сервер RabbitMQ позволяет заранее создавать очереди с необходимыми параметрами. В результате для Поставщика  можно запретить создавать другие очереди. С точки зрения безопасности данный подход самый лучший, т.к. Поставщик будет работать только с той очередью, которую для него создали. Но для упрощения процедуры настройки учетной записи user-sensors будут выданы права на создание очереди.

Шаг 1 — Создание Docker-сети


До создания Docker контейнеров создадим Docker-сеть «mynetwork», IP-подсеть 172.21.0.0/24:

$ docker network create --driver bridge --subnet 172.21.0.0/24 --ip-range=172.21.0.0/25 --gateway 172.21.0.127 mynetwork

Шаг 2 — Создание Docker-контейнера с сервером RabbitMQ


Выполним развертывание сервера RabbitMQ на основе официального Docker-образа — rabbitmq: RabbitMQ is an open source multi-protocol messaging broker. Создадим контейнер с RabbitMQ командой:

$ docker run -d --hostname rabbitmq-iot --name rabbit-iot -v rabbit-iot-config:/etc/rabbitmq -v rabbit-iot-lib:/var/lib/rabbitmq --net mynetwork --ip 172.21.0.5 rabbitmq:alpine

Сервер RabbitMQ использует два TCP-порта: 5672 — для доступа клиентов, 15672 — для управления через web-интерфейс.

По умолчанию веб-интерфейс не работает, но рекомендуется включить для мониторинга состояние очереди. Дополнительно, для отладки рекомендуется опубликовать доступ к порту 5672/TCP, т.к. приложение на API AvaloniaUI неудобно каждый раз запускать на Banana Pi BPI-M64 в режиме отладки. Для тестирования команда по созданию Docker-контейнера будет следующей:

$ docker run -d --hostname rabbitmq-iot --name rabbit-iot -p 5672:5672 -p 15672:15672 -v rabbit-iot-config:/etc/rabbitmq -v rabbit-iot-lib:/var/lib/rabbitmq --net mynetwork --ip 172.21.0.5 rabbitmq:alpine

После успешного запуска контейнера необходимо перейти в консоль Linux для настройки сервера RabbitMQ. Для включения web-интерфейса необходимо выполнить команду:

$ docker exec -ti rabbit-iot rabbitmq-plugins enable rabbitmq_management

Затем перейти по IP-адресу одноплатного компьютера, например: 192.168.43.208:15672. Логин/пароль по умолчанию: guest/guest.

Шаг 2 — Создание виртуального хоста


Создадим виртуальный хост, /host-iot:

$ docker exec -ti rabbitmq-iot rabbitmqctl add_vhost /host-iot


Шаг 3 — Создание пользователей


Для создания пользователей необходимо выполнить команды:

$ docker exec -ti rabbitmq-iot rabbitmqctl add_user user-sensors Password1
$ docker exec -ti rabbitmq-iot rabbitmqctl add_user user-lcd Password2

где Password1, Password2 — пароли учетных записей.

Шаг 4 — Выдача привилегий


Как создавать пользователей и разграничивать доступ, более подробно в документации Authorisation: How Permissions Work. Существует три операции: configure (конфигурирование), write (запись), и read (чтение). Операции назначаются на сущности: exchange (обменник), queue (очередь).

В результате

  • пользователь user-sensors будет обладать правами: configure exchange, configure queue, write exchange, write queue, read exchange, read queue;
  • пользователь user-lcd будет обладать правами только read queue.

Права выдаются путем задания регулярного выражения. Для выдачи прав необходимо выполнить команды:

$ docker exec -ti rabbitmq-iot rabbitmqctl set_permissions -p "/host-iot" "user-sensors" ".*" ".*" ".*"
$ docker exec -ti rabbitmq-iot rabbitmqctl set_permissions -p "/host-iot" "user-lcd" "" "" "queue"

Три параметра после имени пользователя, например «user-lcd», выдают привилегии на операции: configure, write, и read. В первых двух ничего не записано, значит, никаких прав configure и write у пользователя «user-lcd» не будет. Третий параметр выдает права на операцию read, в значении параметра указано «queue», значит, пользователю «user-lcd» выдаются права на чтение данных из очереди.

На этом настройка сервера RabbitMQ закончена.

Приложение WeatherStation.Sensors


Считывает показания с датчиков и отправляет данные на сервер RabbitMQ. Опрос датчиков производится с определенным временным интервалом, который задается в настройках. Если датчик BME280 не удается инициализировать, то будет произведен поиск любого доступного датчика по протоколу OneWire. Проект на GitHub WeatherStation.Sensors. Подключенные Nuget-пакеты:

  • Iot.Device.Bindings;
  • System.Device.Gpio;
  • Newtonsoft.Json;
  • RabbitMQ.Client;
  • Microsoft.Extensions.Hosting.

Файл конфигурации располагается по пути config/appsettings.json. Содержимое файла appsettings.json:

{
  "AppSettings": {    
    "Sensors": {
      "ReadEvery": 50,
      "I2CBusId": 1,
      "BME280Address": 118,
      "GPIOCHIP": 1,      
      "pinLED": 36,
      "pinLED_active_low": true,
      "pinBUTTON": 38
    },
    "RabbitMQ": {
      "UserName": "user-sensors",
      "Password": "PASSWORD1",
      "VirtualHost": "/host-iot",
      "HostName": "192.168.43.208",
      "ClientProvidedName": "app:sensors component:event-consumer",
      "x_message_ttl": 300000,
      "x_max_length": 5,
      "exchangeName": "exchange-sensors",
      "queueName": "queue-sensors",
      "routingKey": "rKey1"
    }    
  }
}

Рассмотрим параметры:

  • Sensors\ReadEvery — интервал времени считывания показаний датчиков в секундах;
  • Sensors\I2CBusId — Id шины I2C, может быть: 0,1,2, и т.д. Если нет, то задать null;
  • Sensors\BME280Address — адрес датчика BME280 на шине I2C, константа в формате byte. Если нет, то задать null;
  • Sensors\GPIOCHIP — Id чипа GPIO. Если нет кнопки и светодиода, то задать null;
  • Sensors\pinLED — номер контакта (GPIOXX, ножки процессора) светодиода. Если нет светодиода, то задать null;
  • Sensors\pinLED_active_low — инвертирование значение вывода для светодиода. Если задать «1», то на логическую «1» светодиод будет выключен, и наоборот;
  • Sensors\pinBUTTON — номер контакта (GPIOXX, ножки процессора) кнопки. Если нет кнопки, то задать null;
  • RabbitMQ\UserName — имя пользователя для подключения к серверу RabbitMQ;
  • RabbitMQ\Password — пароль пользователя UserName;
  • RabbitMQ\VirtualHost — виртуальный хост;
  • RabbitMQ\HostName — IP-адрес или DNS-имя сервера RabbitMQ;
  • RabbitMQ\x_message_ttl — время жизни сообщения в очереди в миллисекундах (мс);
  • RabbitMQ\x_max_length — максимальное количество сообщений в очереди. Все сообщения кроме, будут автоматически удалены сервером RabbitMQ;
  • RabbitMQ\exchangeName — название обменника;
  • RabbitMQ\queueName — название очереди, в которую будут отправляться сообщения;
  • RabbitMQ\routingKey — ключ маршрутизации сообщений.

Минимальная конфигурация датчиков — это наличие BME280 или DS18B20. Если не будет светодиода, но будет кнопка, то кнопка будет работать. Если не будет кнопки, то просто второй экран с графиком не удастся отобразить.

Работа с шиной I2C и считывание значений с BME280 на C#


Весь программный код работы с датчиками располагается в файле Services/ReadSensorsServices.cs. Для работы с шиной I2C предназначено пространство имен: System.Device.I2c. Для датчика BME280 пространство имен: Iot.Device.Bmxx80. Примеры работы с датчиками серии Bmxx80 доступны по ссылке GitHub Bmxx80/samples.

Следующий фрагмент кода инициализирует датчик BME280 и считывает показания:

I2cConnectionSettings i2cSettings = new(_appSettings.Sensors.I2CBusId.Value, _appSettings.Sensors.BME280Address.Value);
I2cDevice i2cDevice = I2cDevice.Create(i2cSettings);
Bme280 bme280 = new Bme280(i2cDevice)
{
   // set higher sampling
   TemperatureSampling = Sampling.UltraHighResolution,
   PressureSampling = Sampling.UltraHighResolution,
   HumiditySampling = Sampling.UltraHighResolution,
   FilterMode = Bmx280FilteringMode.X2
};
//Test Read
bme280.ReadAsync().Wait();

Класс I2cConnectionSettings предназначен для задания настроек I2C устройства, первый аргумент — Id шины I2C, второй аргумент — адрес датчика I2C. В классе Bmx280Base определены константы адреса BME280:

// Default I2C bus address.
public const byte DefaultI2cAddress = 119;
// Secondary I2C bus address.
public const byte SecondaryI2cAddress = 118;

Таким образом, параметр в настройках Sensors\BME280Address=118 соответствует значению SecondaryI2cAddress. Значение Sampling.UltraHighResolution задает максимальную точность замера физических величин.

Если использовать файл DTS sun50i-a64-i2c1-bme280.dts, который включает шину I2C и задействует драйвер получения данных с BME280 через ФС SysFs, и одновременно получать данные из .NET кода, то все это работает без ошибок.

Работа с DS18B20 на C#


Программный код работы с датчиком DS18B20 самый простой. Классы из пространства Iot.Device.OneWire по сути являются обертками над драйвером OneWire в Sysfs. Примеры работы с температурными датчиками по протоколу 1-wire доступны по ссылке GitHub OneWire/samples.

Следующий программный код находит первый доступный датчик, работающий по протоколу OneWire, и считывает его значение:

OneWireThermometerDevice devOneWire = OneWireThermometerDevice.EnumerateDevices().FirstOrDefault();
devOneWire.ReadTemperatureAsync().Wait();

Согласно перечислению enum в библиотеке Iot.Device.OneWire, файл DeviceFamily.cs, помимо DS18B20, поддерживается подключение следующих датчиков, работающих по OneWire-протоколу: DS18S20, MAX31820, DS1825, MAX31826, MAX31850, DS28EA00. Все указанные датчики тоже должны работать, но это неточно. Тестирование было проведено только с датчиком DS18B20.

Работа с кнопкой и светодиодом


Подробно рассмотрено в публикации Управляем контактами GPIO из C# .NET 5. Раздел — Создание приложения обработки прерывания от кнопки. Номера контактов те же самые.

Отправка данных на сервер RabbitMQ


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

В очередь отправляются данные в формате JSON. Пример сообщения:

{
	"HomeTemperature": 30.19,
	"HomePressure": 100254.0,
	"HomeHumidity": 39.0,
	"DateTimeNow": 637622330482396513	
}

Структура данных следующая:

  • Home* — значения физических величин;
  • DateTimeNow — дата и время, считывания показаний датчиков в тиках по времени UTC:  DateTime.UtcNow.Ticks.

Когда срабатывает событие отпускания кнопки, сообщение отправляется незамедлительно. Пример сообщения:

{
	"DateTimeNow": 637622332649242921,
	"Command": "PressButton1"
}

Структура данных следующая:

  • DateTimeNow — дата и время, считывания показаний датчиков в тиках по времени UTC:  DateTime.UtcNow.Ticks;
  • Command — команда, нажата кнопка №1.

Запуск Docker-контейнера


Контейнер для запуска приложения WeatherStation.Sensors — devdotnetorg/dotnet-ws-sensors, доступна сборка только под архитектуру ARM64. Команда запуска контейнера:

$ docker run -d --name test-dotnet-ws-sensors --restart always --net mynetwork --ip 172.21.0.6 --device /dev/i2c-1 --device /dev/gpiochip1 -v /sys/bus/w1/devices:/sys/bus/w1/devices -v /sys/devices/w1_bus_master1:/sys/devices/w1_bus_master1 -v test-dotnet-ws-sensors-config:/app/config -e RabbitMQUserName=user-sensors -e RabbitMQPassword=PASSWORD1 devdotnetorg/dotnet-ws-sensors

Контейнеру необходимо передать доступ к устройствам: /dev/i2c-1 и /dev/gpiochip1. Доступ к OneWire-датчикам осуществляется через Sysfs, поэтому необходимо передать пути OneWire-шины: /sys/devices/w1_bus_master1. Если используется BME280, то пробрасывать volume — /sys/devices/w1_bus_master1 нет необходимости. Переменные окружения RabbitMQUserName, RabbitMQPassword переопределяют значения в config/appsettings.json. Поэтому в целях безопасности логин/пароль доступа к серверу RabbitMQ можно не хранить в конфигурационном файле.

Приложение WeatherStation.Panel.AvaloniaX11


Отображает графический интерфейс на LCD экране. Проект на GitHub WeatherStation.Panel.AvaloniaX11. Подключенные Nuget-пакеты:

  • Avalonia;
  • System.Device.Gpio;
  • Avalonia.Desktop;
  • Avalonia.Diagnostics;
  • Microsoft.Extensions.Configuration;
  • Microsoft.Extensions.Hosting;
  • Newtonsoft.Json;
  • RabbitMQ.Client;

Является классическим Desktop-приложением, построенным на фреймворке AvaloniaUI (кроссплатформенный фреймворк на основе XAML, WPF/UWP). Для первой реализации не использовался подход
MVVM для построения интерфейса. Первоначальная задача заключалась в простом запуске на Linux в Docker-контейнере. Разметка элементов рассчитана на разрешение 320x240 точек. Работа с сервером RabbitMQ такая же, как и в приложении WeatherStation.Sensors. При потери соединения с сервером приложение будет пытаться повторно установить соединение, и пиктограмма соединения с зеленого цвета изменится на красный.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Графический интерфейс в Windows 7

В верхней части экрана отображается текущая дата и время, затем текущая температура, влажность и давление. Последняя строка служит для информирования времени замера показаний датчиков. Если данные были получены менее 10 секунд назад, то отображается надпись — «Данные получены недавно».

Внешний вид в Linux немного отличается:

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Графический интерфейс в Linux Alpine

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

График изменения температуры отображается интерактивным элементом с автоматическим обновлением, поддерживается анимация.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
График изменения температуры

Для построения диаграмм использовалась библиотека LiveCharts2 от Alberto Rodriguez, которая работает на WPF, WinForms, Xamarin и Avalonia. В проект добавлена в виде программного кода, папка /Libs/.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Примеры диаграмм библиотеки LiveCharts2

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

Отображение графика изменения температуры:


Файл конфигурации располагается по пути config/appsettings.json. Содержимое файла appsettings.json:

{
  "AppSettings": {
    "RabbitMQ": {
      "UserName": "user-lcd",
      "Password": "PASSWORD2",
      "VirtualHost": "/host-iot",
      "HostName": "192.168.43.208",
      "ClientProvidedName": "app:sensors component:event-consumer",
      "queueName": "queue-sensors"
    }
  }
}

Параметры подключения к серверу RabbitMQ такие же, как и в приложении WeatherStation.Sensors.

Важное замечание. Разрабатывая графические приложения для Linux в IDE Windows, обращайте внимание на страницу кодировки текстовых строк, ресурсов и файлов исходного кода. Все ресурсы необходимо перекодировать в UTF-8. А то можете увидеть подобное:

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Интерфейс в Linux с кодировкой Windows-1251

Как работает Docker-контейнер


Для запуска приложения необходима графическая подсистема Xorg и оболочка xfce4. Запускается подсистема Xorg, затем xfce4, и срабатывает автозагрузка приложения dotnet /app/WeatherStation.Panel.AvaloniaX11.dll. Пока без xfce4 приложение аварийно закрывается. В следующей версии эта проблема будет решена.

Контейнер напрямую задействует Linux  Framebuffer по адресу /dev/fb0. Адрес пока захардкорен, в следующей версии будет задаваться через переменную. Поэтому вывод изображения возможен на любой LCD-дисплей, SPI LCD, HDMI и VGA.

Запуск Docker-контейнера


Контейнер для запуска приложения WeatherStation.Panel.AvaloniaX11 — devdotnetorg/dotnet-ws-sensors, доступна сборка под архитектуры: ARM64 и x86. Команда запуска контейнера:

$ docker run -d --name test-dotnet-ws-panel --restart always --net mynetwork --ip 172.21.0.7 --privileged --device=/dev/fb0 -v test-dotnet-ws-panel-config:/app/config -e RabbitMQUserName=user-lcd -e RabbitMQPassword=PASSWORD2 -e TZ=Europe/Moscow devdotnetorg/dotnet-ws-panel:avaloniax11

Контейнеру необходимо передать доступ к устройству Linux framebuffer: /dev/fb0. Без выдачи привилегий не запускается Xorg. Переменные окружения RabbitMQUserName, RabbitMQPassword переопределяют значения в config/appsettings.json. Поэтому в целях безопасности логин/пароль доступа к серверу RabbitMQ можно не хранить в конфигурационном файле. Для настройки часового пояса отображения текущего времени необходимо задать переменную «TZ». Значения переменной «TZ» соответствую международному стандарту задания временных зон — List of tz database time zones.

Docker Compose. Особенности запуска контейнеров


Итоговый файл docker-compose.ws.AvaloniaX11.yml для Docker Compose будет следующим:

version: '2.2'

services:
# RabbitMQ
  rabbitmq-iot:
    container_name: rabbitmq-iot
    image: rabbitmq:alpine
#    restart: always
    hostname: rabbitmq-iot    
    ports:
      - 5672:5672/tcp
      - 15672:15672/tcp
    volumes:
      - rabbit-iot-config:/etc/rabbitmq
      - rabbit-iot-lib:/var/lib/rabbitmq
    networks:
      mynetwork:
        ipv4_address: 172.21.0.5
    healthcheck:
        test: rabbitmq-diagnostics -q ping
        interval: 10 s
        timeout: 10 s
        retries: 10
    cpus: 0.6
    
#WeatherStation.Sensors
  test-dotnet-ws-sensors:
    image: devdotnetorg/dotnet-ws-sensors
    container_name: test-dotnet-ws-sensors
    restart: always
    devices:
      - /dev/i2c-1
      - /dev/gpiochip1
    environment:
      - RabbitMQUserName=user-sensors
      - RabbitMQPassword=PASSWORD1
    volumes:
      - /sys/bus/w1/devices:/sys/bus/w1/devices
      - /sys/devices/w1_bus_master1:/sys/devices/w1_bus_master1
      - test-dotnet-ws-sensors-config:/app/config
    networks:
      mynetwork:
        ipv4_address: 172.21.0.6
    depends_on:
      rabbitmq-iot:
        condition: service_healthy
    links:
      - rabbitmq-iot
    cpus: 0.2
                
#WeatherStation.Panel
  test-dotnet-ws-panel:
    image: devdotnetorg/dotnet-ws-panel:avaloniax11
    container_name: test-dotnet-ws-panel
    restart: always
    privileged: true
    devices:
      - /dev/fb0
    networks:
      mynetwork:
        ipv4_address: 172.21.0.7
    environment:
      - RabbitMQUserName=user-lcd
      - RabbitMQPassword=PASSWORD2
      - TZ=Europe/Moscow
    volumes:
      - test-dotnet-ws-panel-config:/app/config
#      - /etc/timezone:/etc/timezone:ro
    depends_on:
      rabbitmq-iot:
        condition: service_healthy
    links:
      - rabbitmq-iot
    cpus: 0.8

volumes:
  rabbit-iot-config:
   name: rabbit-iot-config
  rabbit-iot-lib:
   name: rabbit-iot-lib
  test-dotnet-ws-sensors-config:
   name: test-dotnet-ws-sensors-config
  test-dotnet-ws-panel-config:
   name: test-dotnet-ws-panel-config
   
networks:
  mynetwork:
    external: true

Для установки Docker Compose необходимо выполнить команду:

$ sudo apt-get update
$ sudo apt-get install docker-compose

Для развертывания контейнеров необходимо файл docker-compose.ws.AvaloniaX11.yml скопировать на одноплатный компьютер и выполнить команду в каталоге с файлом:

$ docker-compose -p "DotnetServiceWeatherStation" --file docker-compose.ws.AvaloniaX11.yml up -d

Запускать все контейнеры одновременно не рекомендуется. При одновременном запуске контейнеров с сервером RabbitMQ и приложением WeatherStation.Panel.AvaloniaX11, накопитель данных просто умирает, и система уходит в ступор на 20 минут. При первоначальном запуске сервер RabbitMQ очень сильно загружает операциями I/O накопитель данных. Поэтому в Docker Compose автоматически стартует только два контейнера: WeatherStation.Sensors и WeatherStation.Panel. Запуск сервера RabbitMQ решен с помощью cron задачи. Эмпирическим путем было получено, что 2 минут достаточно для полной загрузки контейнеров WeatherStation.Sensors и WeatherStation.Panel. Выполним проверку работы cron демона, командой:

$ systemctl status cron

Результат выполнения команды:

root@bananapim64:~# systemctl status cron
● cron.service - Regular background program processing daemon
   Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2021-07-16 01:45:28 MSK; 2 days ago
     Docs: man:cron(8)
 Main PID: 707 (cron)
    Tasks: 1 (limit: 2219)
   CGroup: /system.slice/cron.service
           └─707 /usr/sbin/cron -f

Cron демон успешно работает.

Для запуска сервера RabbitMQ создадим cron задачу, пополним команду:

crontab -e

В конец файла добавим строку:

@reboot sleep 120 && docker start rabbitmq-iot

Команда sleep 120, ожидание двух минут. Сервер RabbitMQ запускается примерно 2 минуты, таким образом, на полный запуск всех приложений уходит около 4 минут.

Утилизация ресурсов


Теперь посмотрим, сколько ресурсов потребляют контейнеры. В ОС не запущено никаких дополнительных фоновых процессов и лишних других контейнеров, кроме portainer/portainer. Будем оценивать только потребление оперативной памяти и процессора. Загрузка четырех ядер Cortex-A53 на 100% будет взята за — 100% загрузки CPU. Эквивалентом одноплатного компьютера Banana Pi BPI-M64 по производительности примерно является — Raspberry Pi 3 и китайский вариант Orange Pi Zero 2.

Контейнер devdotnetorg/dotnet-ws-sensors

При первоначальном запуске контейнера потребление RAM составляет около 20 Мб, затем через некоторое время (около 30 минут) падает до ~7 Мб. Загрузка CPU на длительном периоде времени колеблется в диапазоне 1.5-3%, каких-либо скачков загрузки CPU во время старта не зафиксировано.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Потребление ресурсов контейнером devdotnetorg/dotnet-ws-sensors (источник графика — portainer/portainer)

Контейнер devdotnetorg/dotnet-ws-panel

Потребление RAM составляет около 75 Мб. Загрузка CPU на длительном периоде времени колеблется в диапазоне 12-18%. Первый пик, переключение на отображение графика температуры, вызвало увеличение загрузки CPU на 80%. Затем экран был переключен обратно в режим отображения температуры. Второй пик, повторное переключение на график, загрузка CPU 50%, повторная прорисовка графика уже требует меньше ресурсов. Третий пик, в третий раз переключили на график, загрузка CPU 50%. Если с режима отображения температуры переключаться на график и обратно с небольшим интервалом, около 10 секунд, то это не приводит к увеличению загрузки CPU.

Интерпретация полученных данных следующая: первая инициализация графика приводит к загрузке CPU на 80%. После этого, если были добавлены новые точки, которые требуют перерисовки графика, при отображении графика загрузка CPU составляет 50%. Если новые точки не были добавлены, и переключиться на график, то это не приводит к увеличению загрузки CPU. Из этого следует, что отображение графика расходует довольно много ресурсов CPU. Потребление RAM при всех действиях не изменяется т.к. график температуры находится в памяти в независимости от режима отображения.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Потребление ресурсов контейнером devdotnetorg/dotnet-ws-panel (источник графика — portainer/portainer)

Запуск метеостанции на Banana Pi M64 (полный вариант):

Вывод


Данная реализация проекта была проверкой работоспособности первоначальной идеи работы с GPIO из Docker-контейнеров и вывода полноценного UI на LCD без использования популярного фреймворка Qt for Embedded Linux. Результаты оказались более чем положительными. Производительности SoC Allwinner A64 (был выпущен в 2015 г.) оказалось достаточно для выполнения подобных задач. Загрузка контейнера devdotnetorg/dotnet-ws-sensors всего на 3% дает возможность запустить с десяток подобных контейнеров. Единственное, обнаружилось узкое место в низкой скорости обмена данными с microSD. Если использовать более быструю память eMMC, то, скорее всего, ситуации с долгой загрузкой можно избежать. Использование сервера RabbitMQ все же тяжеловато для подобного устройства. Желательно использовать более легкий брокер сообщений. У кого есть предложения, как говорится, welcome в комментарии.

Что дальше?


Нет пределу совершенству, проект будет переписан, переработан и переделан, а именно:

  • Изменить архитектурный шаблон в приложении WeatherStation.Sensors. При реализации была допущена ошибка в построении архитектуры приложения, которая приводит к невозможности нормального завершения работы. Ошибка была обнаружена когда приложение уже было готово.
  • Решить проблему с выставлением культуры. На данный момент, не смотря на принудительную смену культуры на «ru-RU», интерфейс все равно остается на «en-US».
  • Рассмотреть замену сервера RabbitMQ на другого брокера сообщений. Сервер RabbitMQ сильно загружает операциями I/O накопитель данных, что потенциально может привести к длительной блокировке устройства.
  • Повысить безопасность. При формировании Docker-контейнеров применить лучшие практики и воспользоваться руководством Безопасность встраиваемых систем Linux.
  • Исключить использование xfce4. В приложении WeatherStation.Panel.AvaloniaX11 решить проблему с исключением при запуске поверх Xorg. В результате снизится нагрузка на CPU и уменьшится объем занимаемой оперативной памяти.
  • Собрать контейнер для ARMv7. Разобраться в проблеме сборки контейнера для архитектуры ARM32.
  • Сделать адаптивный интерфейс. В приложении WeatherStation.Panel.AvaloniaX11 сделать разметку для экранов с различным разрешением.
  • Добавить темную тему. Для приложения WeatherStation.Panel.AvaloniaX11 включить возможность использования темной темы.
  • Добавить управление яркостью экрана. Добавить еще один Docker-контейнер с приложением, которое будет получать данные по уровню освещения от датчика TSL2561 Light Sensor, и генерировать аналоговый сигнал с помощью MCP4725 I2C DAC для управления яркостью LCD ILI9341. Также будет доступно управления яркостью через драйвер gpio-backlight в случае данной поддержки у дисплея. Заодно практически будет проверена работа с шиной I2C в режиме совместного доступа из различных контейнеров.
  • Сделать реализацию графического интерфейса на фреймворке Uno Platform с использованием только Linux Framebuffer. Фреймворк Uno Platform, так же как и Avalonia, позволяет запускать графические приложения поверх Xorg. Сравнить реализации по используемым ресурсам компьютера.
  • Сделать реализацию графического интерфейса с использованием фреймворка Uno Platform с использованием только Linux Framebuffer. Uno Platform позволяет нативно выводить приложение на виртуальное устройство для вывода графики Linux Framebuffer без использования какой-либо графической подсистемы в виде Xorg, и это точно. Было проведено успешное тестирование на Banana Pi BPI-M64. С точки зрения минимизации потребления ресурсов идеальный вариант.

Ресурсы


Проект на GitHub — dotnet-service-weather-station.
Docker-контейнеры: devdotnetorg/dotnet-ws-sensors, devdotnetorg/dotnet-ws-panel.

Битва графических интерфейсов: Avalonia и Uno Platform VS. HTML (Node.js, Electron)


Теперь рассмотрим собственно суть подхода. Автор предлагает запускать Web-браузер на самом устройстве для отображения HTML-страницы (интерфейс управления). HTML-страница загружается с http-сервера, в данном случае с Node.js. Таким образом, система отображения состоит из трех компонентов:

  • Http-server и web-приложение — сервер/приложение отдачи контента;
  • Веб-браузер — среда исполнения скриптового кода;
  • Контент — скриптовый код, исполняемый в web-браузере.

Первое с точки зрения эксплуатации системы, браузер является лишним звеном, которое необходимо дополнительно обслуживать. Второе, когда в браузере вы отображаете «Hello word!», все работает нормально. Но все меняется, когда вам необходимо отобразить графический интерфейс со сложной структурой на устройстве с малой производительностью. Дополнительно браузер съедает достаточно много оперативной памяти. Запускать все в Electron? На моем компьютере с процессором Intel Core i7-3520M запуск «насыщенных» приложений Electron приводит к тому, что вентиляторы начинают работать так, как будто я запускаю Watch Dogs 2. Это хорошо, если ваше устройство работает от электросети, а если используется автономный источник питания? Так что Electron — просто безумно тормозная и очень требовательная к ресурсам штука. Native-app must have!

Третье, другая серьезная проблема — это отслеживание ошибок в JS-коде. Сервер отдает только JS-код и контент в формате JSON. Отслеживание ошибок в JS-коде и отправки стека исключения на сервер, еще та затея. Основная моя претензия к построению интерфейса на HTML заключается в низкой производительности и существенно худшем контроле со стороны устройства, что отображается на экране у пользователя. Можно добавить обработчики исключений в JS-код. Но вам придется контролировать исключения в Node.js, JS-коде на стороне браузера и контролировать работу браузера.

Программный код Avalonia и Uno Platform выполняется нативно в системе и будет всегда быстрее JS-кода исполняемого в контексте браузера. А в случае вывода графики напрямую на Linux Framebuffer, не расходуются ресурсы на Xorg и браузер.

Теперь поговорим о кадрах. Встраиваемое решение можно разделить на две части/приложения. Первое приложение — работа с GPIO и другим устройствами. Второе приложение — собственно сам графический интерфейс. Если графический интерфейс разрабатывать на Node.js, то потребуется разработчик с квалификацией программирования на JS и/или TypeScript. Первое приложение будете разрабатывать на C или C#. Таким образом, в команде должны быть разработчики с разным набором квалификации.

Если графический интерфейс разрабатывается на AvaloniaUI, и работу с датчиками реализовать на C# коде, то вам достаточно будет разработчиков с квалификацией по .NET (C#). В результате у вас будет однородная команда, что существенно упрощает кадровый вопрос.

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

«Программное обеспечение для них, как правило, зависимо от операционной системы, и попытка апгрейда любого компонента устройства (например, замена дисплея на более совершенную модель) часто оборачивается серьезной проблемой.». Нет, не оборачивается серьезной проблемой. История развития аппаратного и программного обеспечения — это история развития стандартизации и унификации. Если вы смените на Raspberry Pi один монитор с HDMI интерфейсом на другой, то же с поддержкой HDMI, то вам не придется переписывать никакое ПО. Единственно, возможно, потребуется поменять некоторые настройки для решения проблем с мерцанием изображения. Но это не переписывание основного прикладного ПО.

Если в моем примере решите заменить LCD ILI9341 на SPI-интерфейсе, на другой дисплей, например на MIPI Display Serial Interface (MIPI DSI — наиболее часто используется в смартфонах и планшетах) интерфейсе, то вам, безусловно, под новый дисплей потребуется новый драйвер, который может предоставить разработчик дисплея. Но переписывать приложение, например, такое как WeatherStation.Panel.AvaloniaX11 не потребуется, и это точно. Приложение WeatherStation.Panel.AvaloniaX11 использует абстрактное виртуальное устройство Linux  Framebuffer, которое является платформонезависимым. API работы c данным устройством не зависит ни от модели конечных графический панелей, ни от аппаратной архитектуры: x86, ARM, RISC-V.

Вы готовы потратить ресурсы на разработку этого приложения и драйверов? Будете ли вы создавать их для каждой из популярных операционных систем: Windows, OSX, Linux? Не забудьте еще и о мобильных девайсах. Нет, и в этом нет проблемы. Если вы разрабатываете встраиваемое решение, то вас, скорее, будет волновать совместимость в пределах одной из платформ. Если это Linux, то решение должно работать на Ubuntu, Debian и т.д. Но если вы используете Docker-технологию для развертывания прикладного ПО, то эта проблема практически решается. А использовать железо с OSX — дорогое удовольствие. Windows — это отдельная тема, ее популярность в секторе встраиваемых устройств уже не такая как в 2000-х годах. Сейчас все большую роль играют Linux-решения и решения, основанные на Linux-ядре (Android). Но, не смотря на это, приложение WeatherStation.Panel.AvaloniaX11 прекрасно работает на Windows 7,10 (x86) и Linux (x86, ARM64), причем без какого-либо переписывания исходного кода. А если графический интерфейс построить на Uno Platform, то тот же программный код будет работать в Windows, iOS, macOS, Android, Linux и напрямую выводить изображение через Linux Framebuffer.

Почему же мы до сих пор не наблюдаем таких устройств? На самом деле, они есть: такие интерфейсы есть почти у всех роутеров и точек доступа, и они давным-давно отлично работают. Да, на роутере есть Web-интерфейс, но рендером HTML-странички занимается CPU и GPU моего компьютера. Роутер лишь отдает статику (файлы html, js) и обслуживает GET и POST запросы с JSON и/или XML данными. Я без особых проблем могу сделать приложение на ASP.NET Web API, прикрутить для статики nginx и все это запустить в Docker на Banana Pi BPI-M64. Затем нарисовать крутой Web-интерфейс и показывать его на своем компьютере. Только вот рендером крутой графики будет занимать мой компьютер, а не Banana Pi BPI-M64. И в этом заключается большая разница.

Как насчет задач, которые необходимо решать в реальном времени? И это не проблема. Помимо самого очевидного пути с установкой дополнительных микроконтроллеров, для действий в реальном времени можно использовать DMA. Вот вам работающий пример — PyCNC, который безо всяких дополнительных микронтроллеров управляет драйверами шаговых электродвигателей. В принципе да, но неплохо было бы еще рассказать и о недостатках DMA и заодно расшифровать, что это значит. DMA (Direct Memory Access) — прямое обращение к памяти в Linux по пути /dev/mem. Управлять GPIO путем прямой записи в регистры памяти дает большой выигрыш в скорости, но приводит к двум серьезным проблемам:

  • Низкая надежность. Манипулировать регистрами памяти необходимо с ювелирной точностью и четко следить за их состоянием. Запись в соседний регистр в случае переполнения буфера может привести к краху операционной системы. А если залезете на регистры управления электропитанием, то можете из своего устройства сделать запеканку;
  • Зависимость от SoC на одноплатном компьютере. Адресация регистров памяти для каждого чипа является индивидуальной, а значит, для каждого процессора придется переписывать драйвер обращения к памяти.

Для работы с контактами GPIO в посте Управляем контактами GPIO из C#  были приведены результаты работы товарища ZhangGaoxing. Он для платы Orange Pi Zero написал драйвер поддержки процессоров Allwinner H2+/H3 под .NET платформу. В самом начале, я тоже хотел сделать подобный драйвер для Allwinner A64. Поняв, какие есть нюансы с безопасностью, и по факту можно запускать лишь один Docker-контейнер для работы с GPIO, взял на вооружение библиотеку Libgpiod. Данная библиотека гарантирует надежность и безопасность работы с GPIO и является  платформонезависимой. Использование подхода DMA подойдет далеко не для всех проектов.

Но веб-приложение работает медленно! Конечно, это ненативное приложение. Но такая ли низкая скорость у интерфейса вашего роутера? У Slack, Skype или редактора Atom? Три последних примера — такие же приложения на базе Electron, и медленными их не назовешь. Без подтверждения быстрой работы этих приложений на Raspberry Pi сложно в это поверить. А Skype даже на моем компьютере временами лагает. Может быть и быстро работает, но хотелось бы видеть конкретные примеры.

Наконец, ваш исходный код веб-приложения можно спокойно перенести, если в будущем вы решите сменить железо на более современное. Приложение почти всегда продолжает работать и после этого. Сейчас этим никого не удивишь! Приложение WeatherStation.Panel.AvaloniaX11 на C# ( AvaloniaUI) работает просто с компиляцией для x86 архитектуры, ничего фантастического.

Для примера сделаем гипотетическую модернизацию до Intel Core i5 и запустим Docker-контейнер devdotnetorg/dotnet-ws-panel в Ubuntu 20.04.2.0 LTS (Focal Fossa). Среда исполнения: виртуальная машин x86 в VMWare Workstation:


Подведем общий итог


Каких-либо особых преимуществ использования графического интерфейса HTML на самих устройствах не проглядывается. Согласен, в каком-то смысле удобно. Но платишь за это усложнением решения, появляются лишние компоненты и проблемы, и это более требовательно к ресурсам устройства.

Как верно заметил Stanejkee в комментариях, предложенный вариант является ничем иным как переключением работы браузера в режим Kiosk. Во многих торговых центрах, холлах гостиниц, выставках размещают информационные стенды. Внутри них обычный компьютер x86 с материнской платой в форм-факторе Mini-ITX со встроенным процессором, например, ASROCK J4105-ITX. С лицевой стороны обычный монитор с прикрученной touch-панелью, которая выполняет функции мыши. Внешний вид такой системы не походит на привычный офисный ПК, но по факту то же самое. Для контента просто берете любой сайт и заталкиваете его в браузер, и все готово! Если вам необходимо уже готовый сайт показать в публичном месте, то, безусловно, этот вариант прекрасен. Но если необходимо разработать интерфейс для технологического оборудования, домашней бытовой техники и т.д., подход к построению интерфейса будет совершенно другой.

Обновление от 05.08.2021


Запуск приложения WeatherStation.Panel.AvaloniaX11 без использования Xfce4


Удалось запустить приложение с графической панелью на Xorg без установки Xfce. Проблема заключалась в отсутствии пакета менеджера шрифтов и самих шрифтов от MS. После добавления в файл Dockerfile.WS.Panel.AvaloniaX11.alpine, пакетов msttcorefonts-installer и fontconfig, приложение заработало. Проблема описана в Avalonia Issues: System.InvalidOperationException: Default font family name can't be null or empty #4427 и Avalonia on Raspbian. Контейнер с запуском только на базе Xorg — dotnet-ws-panel:avalonia-xorg. Потребление RAM снизилось на 25 Мб, по сравнению с образом Xfce4, и составило 50 Мб. Нагрузка на CPU составила 8-12% по сравнению с 12-18%.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Потребление ресурсов контейнером devdotnetorg/dotnet-ws-panel:avalonia-xorg (источник графика — portainer/portainer)

Соответственно, загрузка интерфейса на старте системы ускорилась. Но перестал работать полноэкранный режим, и он в Xorg не работает.

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Графический интерфейс без использования Xfce4 на HDMI-панели 7-Inch-1024x600 Display Kit, одноплатный компьютер Cubietruck (ARM32).

Проблема была решены путем запуска утилиты xrandr. Данная утилита возвращает ширину и высоту экрана в точках, соответственно получаем значения и выставляем их окну.

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

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Графический интерфейс в окружение Xfce4 на HDMI-панели 7-Inch-1024x600 Display Kit, одноплатный компьютер Cubietruck (ARM32).

Контейнеры для архитектуры ARMv7 (ARM32)


Контейнеры собираются для различных платформ, таких как x86, ARM, RISC-V, с помощью инструмента Buildx. При сборке проекта под ARM32, шаг «dotnet restore ...» завершался с ошибкой. В конечном итоге, перебрав все возможные варианты, остановился на варианте запуска сборки на одноплатном компьютере Cubietruck для платформы ARM32. Образы для данной платформы в названии тега содержат: *-armhf. Из-за давней проблемы с вызовом функций, связанной с временем в Docker и Alpine, образ построен на основе Debian 10 (buster). И внезапно решилась проблема с культурой, теперь месяц и день недели отображается на русском языке, как и задумывалось:

Weather station Banana Pi BPI-M64 Linux C# Docker RabbitMQ AvaloniaUI
Контейнер с графическим интерфейсом на основе образа debian:buster, одноплатный компьютер Cubietruck (ARM32).

Теперь контейнеры можно запускать на Raspberry Pi, начиная со второй версии, соответственно, кроме моделей Zero и Pico.

Комментарии (62)


  1. 13werwolf13
    02.08.2021 14:28
    +8

    docker и rabbit для метеостанции это не "с пушки по воробьям"?


    1. devzona Автор
      02.08.2021 14:44

      Docker - просто отличное решение, очень удобно развертывать приложения и обеспечивать совместимость. Брокер сообщений, тоже классная штука. Если есть предложения по замене RabbitMQ, давайте обсудим.


      1. verback2308
        02.08.2021 16:06
        +1

        Как вариант замены RabbitMQ - https://docs.coravel.net/


        1. devzona Автор
          02.08.2021 16:12

          Интересное решение. Но за графическую панель администрирования просят денег.


      1. Thar0l
        10.11.2021 13:16

        Ну вместо контейнеров докера для разделения сервисов можно использовать systemd, а для обмена сообщениями - dBus. На дебиане все уже есть из коробки.


        1. devzona Автор
          15.11.2021 02:01

          Контейнеры не имеет смысла менять на systemd. Плюс контейнеров заключается в атомарности образа, чего не скажешь в случае запуска приложения под systemd. dBus хороший сервис, но ограничен одним хостом


    1. devzona Автор
      02.08.2021 18:38
      +1

      Вы посмотрите что внутри Docker-файла. Можете все это вручную устанавливать. Не проблема. Docker не создает практически никакой overhead. Поэтому не вижу никаких проблем использования Docker на подобный устройствах. Нагрузку на CPU вы сами видели.


      1. 13werwolf13
        02.08.2021 18:41

        Здесь не оверхед а оверинженеринг, и усложнение схемы не дающее никакого профита. Вы бы ещё кубер на одной ноде развернули ради пары сервисов. Замените докерфайлы на service файлы для systemd.

        А по поводу кролика я пожалуй погорячился, немного погуглив я пришёл к выводу что он там к месту.


        1. devzona Автор
          02.08.2021 18:58

          Если сравнивать напрямую с МК, то да это сложнее. Но не быть же ретроградом. А в пятерочке терминал на Windows Embedded 5.0, который сканирует штрих-код на товаре и показывает на экране стоимость товара, это не оверинженеринг? Почти прекрасно работает, иногда кэш забивается и устройство переходит в состояние недоступности, но это давняя проблема старых версий Windows Embedded. Да будет и кубер, в концепции отказоустойчивого управления устройством. Два компьютера будут управлять одним внешним устройством, для обеспечения надежности в "особых " системах. "Замените докерфайлы на service файлы для systemd." - можно подумать это решить задачу с обновлением пакетов и с откатом к предыдущей версии. Брокер сообщений оказался очень удобным средством передачи данных, единственное по ресурсам тяжеловат. Нужно заменить на что-то полегче.


          1. 13werwolf13
            03.08.2021 07:48

            можно подумать это решить задачу с обновлением пакетов и с откатом к предыдущей версии.

            вам ниже уже написали про пакеты и репозиторий


      1. isden
        02.08.2021 18:43

        Насчет оверхеда вы правы, но меня терзают некоторые сомнения в ресурсах собственно sd карты при подобном использовании. У меня сейчас в процессе ковыряния нечто похожее (home assistant в докере) и вот думаю, стоит ли в overlayfs все это загнать или не заморачиваться.


        1. 13werwolf13
          03.08.2021 06:17

          стоит отказаться от sd карточки, а танцевать с overlayfs.. ну незнаю.. вроде бы и правильно, но как-то мне не понравилось.


          1. isden
            03.08.2021 09:52

            Ну варианты тут такие:


            • В интернетах пишут, что у некоторых extreme/pro/industrial карточек сделан wear leveling разной степени приличности. Но все сводится к слухам и мутным спекам. Ну кроме industrial, тут уже от производителя спеки видел, там даже health report'ы реализованы. Но у них и цены заметно выше, и в продаже есть совсем не везде.
            • Подключить нормальный SSD через USB3. Смущает надежность такого решения, плюс дополнительная коробка на проводе.
            • Искать одноплатник с m.2. Читал что есть и такие, но в продаже не встречал.


            1. 13werwolf13
              03.08.2021 10:06

              первое по моему опыту не сильно помогает

              второе юзаю, правда не ssd и не 3.0, обычный хард завалявшийся в столе через китайский usb2.0 бокс прелесть которого в том что питается он по отдельному шнурку от отдельного бп. проблем нэма, работает прекрастно, если сдохнет заменить быстро и дёшего, а если зарезервировать каким нибудь mdadm или btrfs mirror можно вообще расслабить булки

              одноплатники с m.2 видел, но не щупал


            1. devzona Автор
              03.08.2021 12:13

              Можно запускать систему на eMMC. На Banana Pi M64 напаяно 8 Гб, вполне достаточно. eMMC не использовал по двум причинам: 1) лишний раз не тратить ресурсы микросхемы; 2) понизить технические характеристики для явного обнаружения узких мест. На некоторых платах, таких как Cubieboard и Cubietruck размещен разъем SATA. Cubietruck с SATA показывает прирост скорости произвольного доступа по сравнению с microSD на Banana Pi M64 до 50%. Но нужно учитывать, что на Cubietruck установлен достаточно старый SoC. С m.2 продается неплохая плата ROCK PI 4C с поддержкой NVME до 2 Тб.


  1. N-Cube
    02.08.2021 15:10

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


    1. devzona Автор
      03.08.2021 12:15
      +1

      Зачем, если есть Armbian и он вполне устраивает?


  1. AxisPod
    02.08.2021 15:33
    +3

    Лет через 10 ждём метеостанции на базе топовых десктопов.

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


    1. devzona Автор
      02.08.2021 16:30

      В самом начале - "Пример метеостанции является демонстрацией встраиваемого решения работы с GPIO, датчиками и вывода пользовательского интерфейса напрямую на LCD". Метеостанция не цель, а техническое демо-решение, которое в дальнейшем будет обрастать различными функциями. И я не скажу что плата Banana Pi M64, такая уж мощная. Да на Arduino метеостанцию сделать проще, а если вы желаете управлять домом с помощью Home Assistant? Управлять голосом, жестами, распознавание видео? Arduino тут уже явно не справится. Сейчас я уже из .NET кода получаю изображения с usb-webcam и других камер. Затем добавлю распознавание голоса. Далее, остается добавить хорошую аудио-плату, и вот тебе умная-аудиоколонка-видеоплеер-на-C#. Без утечки данных "левым структурам". Нагрев процессора, прям сейчас нагрев процессора +51 С, при комнатной +27 С.


      1. assad77
        03.08.2021 13:13
        -1

        Но здесь не решается задача по созданию решения типа mojordomo/homeassistant. Если бы решалась, то основной проблемой было бы создание инфраструктуры, а не использование докера, в качестве серебрянной пули.

        Поэтому и решение выглядит очень громоздким, в то время, как можно было сделать всё проще для создания метеостанции.

        Если хотелось разобраться с docker, то и заголовок лучше исправить. Типа “разбираемся с docker”.


        1. devzona Автор
          03.08.2021 13:39

          Мне следовало сначала написать обобщенный пост, а потом уже техническую реализацию разных модулей. Исправлюсь, напишу что имелось в виду и какова конечная цель. Цель не в создание метеостанции, а в создание демо-проекта проверки технологий, проведение испытаний технологий в малых системах. Если задавать технический заголовок, то он будет очень длинным. Потому что необходимо охватить: Docker, работу с устройством Linux framebuffer, брокер сообщений RabbitMQ  и AvaloniaUI. А с Docker проблем нет, он просто работает, и про него нечего писать. Единственный момент, в контейнер передается напрямую устройства /dev/fbX и другие устройства GPIO. Может быть, на этом следовало бы заострить внимание. Потому что подобный подход решает многие проблемы с безопасностью, которые в Windows Embedded решаются еще теми петлями. На Linux все получается более лаконично и просто. Но сравнение с Windows Embedded это уже другая тема.


  1. isden
    02.08.2021 15:58

    А зачем подтягивающие резисторы на I2C?
    На Banana Pi их разве нет? Да и на самой плате BME280, судя по картинке, они тоже есть.


    1. devzona Автор
      02.08.2021 16:39

      На сколько помню у Banana Pi нет подтягивающих резисторов. На BME280 есть. Подтягивающие резисторы на I2C добавлены для "универсального" решения. В текущей конфигурации все это работает бесперебойно. Скорее всего в дальнейшем, датчик BME280 перенесу на другую плату. И к Banana Pi буду подключать другие I2C датчики, некоторые из них без подтягивающих резисторов.


      1. isden
        02.08.2021 16:44

        Я так краем глаза видел, что в Banana Pi есть внутренние pullup/down резисторы, и режим пинов можно выбирать программно. Возможно ошибаюсь.
        Подтягивающие резисторы, емнип, влияют на скорость работы шины I2C, и не стоит дополнительно ставить их просто на всякий случай.


        1. devzona Автор
          02.08.2021 17:00

          Для I2C-1 на схеме Banana Pi M64, Pull Up/Down резисторов не нашел. Для некоторых пинов в самом SoC можно включать pullup/down, но не для пинов I2C. Согласен, момент про скорость необходимо было указать.


          1. GennPen
            02.08.2021 17:54
            +1

            Не все контроллеры поддерживают подтяжку пинов в режиме I2C.

            Например, с STM32 с которыми недавно работал:
            STM32F4-серия - поддерживает подтяжку в I2C
            STM32F1-серия - не поддерживает. Но если места в обрез на пару лишних резисторов, то можно запараллелить на неиспользуемые пины, которые включить на вход и подать подтяжку.


  1. Javian
    02.08.2021 16:06

    Самое сложное в создании метеостанции - корпус и энергопотребление. Но этот вопрос почему-то возникает когда "всё заработало".


    1. devzona Автор
      02.08.2021 17:02

      Почему корпус является сложной задачей? Если есть доступ к 3D принтеру, то можно без проблем напечатать. Энергопотребление не замерял. Купил для платы аккумулятор на 3V3, подключу к контроллеру питания, сделаю замеры. Дополню текущий пост или опубликую данные по потреблению в следующем посте. Не все сразу)


      1. isden
        02.08.2021 18:36

        Orange Pi тоже на SoC Allwinner потребляет где-то в районе 300-500 мА минимум, от 5В. Так что на особо длительную работу от батарей можно не расчитывать.


        1. devzona Автор
          02.08.2021 19:01

          Все зависит от режима работы процессора и сколько потребляет периферия. В данном случае больше всего потребляет энергии LCD ILI9341.


      1. Javian
        03.08.2021 21:48

        Потому, что от корпуса зависит достоверность данных.
        Для себя я выбрал проект на ESP8266 https://habr.com/ru/post/525140/ - потребляемый ток 0,05А. В момент проведения измерений датчиком SDS011 - 0,14А

        При желании к нему подключаются:

        1. OLED SSD1306

        2. OLED SH1106

        3. LCD 1602 (I2C: 0x27, 0x3F)

        4. LCD 2004 (I2C: 0x27, 0x3F)

        5. SDS011 (Датчик пыли)

        6. Honeywell PM (Датчик пыли)

        7. Sensirion SPS30 (Датчик пыли)

        8. DHT22 (Температура, Относительная влажность)

        9. HTU21D (Температура, Относительная влажность)

        10. BME280 (Температура, Относительная влажность, Давление воздуха)

        11. BMP280 (Температура, Давление воздуха)

        12. SHT3X (Температура, Относительная влажность)

        13. Digital Noise Measuring Sensor (LAeq)

        14. DS18B20 (Температура)

        15. Plantower PMS

        16. BMP180 (Температура, Давление воздуха)

        17. GPS (NEO 6M)


        1. devzona Автор
          04.08.2021 04:01

          Для этого вообще существует отдельная специальность - промышленный дизайн. Понятно дело, датчик спрятанный в глубине корпуса будет показывать уже температуру внутри корпуса, а не в комнате. Так всегда, напечатать на 3D принтере не проблема, хоть завтра, но вот сделать правильный и красивый корпус, тут необходимо весьма подумать.


  1. kibizoidus
    02.08.2021 16:51
    +3

    ... 2137 год.

    Производитель: Новый промышленный одноплатный микрокомпьютер доступен к заказу! Новый Potato Pi 4DX содержит 64 4GHz ядра, терабайт DDR16-FX, Quadx4 USBD7.2 и оптические порты для всех доступных видов имплантов! И все это стоит всего 24.99$ и вмещается в плату размером с четвертак!
    Покупатель: Отлично, наконец сделаю метеостанцию с честными 30FPS на экране...


    1. devzona Автор
      02.08.2021 17:09
      +2

      Круто! заверните мне 10 штук. Да, вы правы, но так развивается ИТ отрасль. Просто с течением времени, стоимость железа нивелируется с затратами на разработку. И разработчику проще использовать одноплатный компьютер и разрабатывать решение на языковых средства высокого уровня, чем искать программиста который сможет оптимизировать код до пару килобайт памяти.


      1. isden
        02.08.2021 18:37
        +1

        Берете какой-нибудь esp32 и пишете код на питоне/lua :)
        Можно подключить тот же дисплей и те же датчики.


        1. devzona Автор
          02.08.2021 18:42

          OK. А как на ESP32 развернуть Home Assistant, сделать распознавание аудио, видео, локально без отправки на удаленный сервер, красивые графики и панель на 9 дюймов как подключить? Это все будет в продолжение.


          1. isden
            02.08.2021 18:46

            Ну речь то про ЯП высокого уровня была :)
            Если вам все это нужно в одном дивайсе, то ок, никаких вопросов.


            1. devzona Автор
              02.08.2021 19:10
              +1

              Мой подход заключается в постепенном наращивание функциональности, и создания "кубиков" приложений для универсального одноплатного компьютера. Хочешь метеостанцию, воткни датчик BME280 и загрузи нужный Docker-контейнер. Хочешь умную колонку, воткни хорошую плату с ЦАП, и загрузи Docker-контейнер. Идея заключается в конструирование устройства. Где брокер-сообщений является внутренней универсальной шиной передачи данных. А Docker-контейнеры сервисами обслуживающие периферийные устройства. Похоже это нужно было вынести в самое начало поста)


              1. isden
                02.08.2021 19:14

                Да, тогда вопросов бы не было :)
                По идее, подобные одноплатники с гребенкой gpio для подобного и задумывались.


      1. Tangeman
        02.08.2021 20:14
        -1

        Просто с течением времени, стоимость железа нивелируется с затратами на разработку.

        Если вы планируете выпуск (допустим) миллиона устройств (без наворотов с распознаванием голоса а с чистым функционалом метеостанции и графиками) то гораздо экономичней будет таки оптимизированная разработка.


        Грубо говоря, железо под *pi стоит $15, а под что-то а-ля Arduino/STM32/etc — стоит $5 — вот уже вам 10 миллионов разницы в массовом производстве. И если разработчику придётся заплатить $50000 вместо $10000 — это гораздо менее существенно на фоне затрат на железо.


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


        1. devzona Автор
          02.08.2021 22:47

          Ключевое слово "Если". Ни кто не планирует продавать метеостанцию на банане. И далее будет распознавание голоса и видео с с Home Assistant. В самом начале написано: "Пример метеостанции является демонстрацией встраиваемого решения работы с GPIO...". Это техническое демо, а не законченная система для продажи. И вывод прочитайте, все же: "Данная реализация проекта была проверкой работоспособности первоначальной идеи работы с GPIO из Docker-контейнеров и вывода полноценного UI на LCD ...". Блин, похоже никто не осилил статью, и до конца не смог дочитать.


          1. Tangeman
            02.08.2021 23:36

            Зря вы так — я прочитал и вполне осилил, но в вашем комментарии на который я отвечал речь явно не идёт про данную конкретную разработку — поскольку вы говорите "Да, вы правы, но так развивается ИТ отрасль" и далее про нивелирование затрат — но как раз в отрасли в общем затраты далеко не всегда нивелируются, на что я и обратил внимание.


            1. devzona Автор
              03.08.2021 00:26

              Прошу меня извинить, если быканул. Как раз таки вы написали "Если вы планируете выпуск (допустим) миллиона устройств". Вы указали на конкретную разработку, но не суть важна. Просто есть компания Toradex и ее успешный бизнес. Я очень много взял материала с их сайта. В своих системах они успешно используют Docker. И тут в комментариях начинается, вопросы про стрельбу из пушки по воробьям. Ребята, нужно понимать что мир на МК не заканчивается. Это не разработка метеостанции, это проверка технологий для использования на подобных малых одноплатных компьютерах, в тех сферах где Arduino/STM32 не будет использоваться в качестве основной системы. Да признаю, это нужно было четко написать в самом начале. Материал по факту это наработка технологий и проверка рабочих гипотез, и все. Я просто видел много систем, где брали плату на STM32 и подключали к ноутбуку. Хотя на самом деле можно было прекрасно обойтись платой в стиле банана с подключенным монитором или LCD с емкостным экраном.


  1. PR200SD
    03.08.2021 08:48

    Делал когда-то на простом esp8266, температура, влажность, давление. Вместо дисплея web интерфейс и modbus TCP с mqtt для внешнего доступа. Плюс еще логи на microSD.

    Тут есть и прошивка и демонстрация:


    1. devzona Автор
      03.08.2021 12:25

      Прикольный интерфейс, круглый циферблат можно добавить как скин.


      1. PR200SD
        03.08.2021 15:57

        В общем да, но в js варианте у него много динамических настроек, светодиоды, ограничения на шкале. Как скрин я их на круглый дисплей GC9A01 ставил.


        1. devzona Автор
          04.08.2021 03:51

          В моем случае нет js и html. Буду смотреть какие есть графические библиотеки для Uno Platform.


  1. Gudd-Head
    07.08.2021 09:03

    Сотые доли градуса...

    ЗАЧЕМ???


    1. devzona Автор
      07.08.2021 17:06

      Потому что точность датчика позволяет. Сотые доли по ощущениям определить сложно, но изменение температуры с шагом в 0.3 градуса уже чувствуется.


      1. Gudd-Head
        12.08.2021 10:24

        Сильно сомневаюсь, что точность.

        Скорее, разрешающая способность.


        1. devzona Автор
          12.08.2021 16:30

          Как раз таки верно, давление — 0.01 hPa ( < 10 cm). Температура — 0.01° C. Влажность – 3%.


  1. YMA
    11.08.2021 11:49

    Кстати, может кто подскажет - есть ли готовые решения выносных метеодатчиков с передачей данных через BLE? Чтобы сразу с корпусом, работающий от AA батарей или 18650, и отдающий температуру, влажность и давление?

    Есть RPi, eInk экран для нее, хочется сделать без лишних проводов. Датчик вывесить за окно в тень и получать оттуда данные на стоящий в квартире RPi.


    1. devzona Автор
      12.08.2021 03:22

      Готовые решения будут существенно дороже самосборных и обладать один/двумя недостатками. 1) привязка передачи данных к своим облачным сервисам, как это реализуют "умные" розетки, светильники и т.д. 2) свой аппаратно/программный центр сбора данных. ИМХО, самое простое и универсальное решение это взять связку:

      18650 Lithium Battery Shield V8

      ESP-WROOM-32

      температурный датчик BMx

      В ESP-WROOM-32 загрузить прошивку ESPHome. Настроить соединение по BLE - BLE Client Sensor. На одноплатный компьютер установить Home Assistant нативно или в варианте Docker контейнера. Для отображения панели управления на экране, запускать браузер как в примере.


      1. YMA
        12.08.2021 10:44

        А вот это не то, что может подойти? Long Range IP67 NRF52820 Outdoor Bluetooth 5.2 Beacon with Temperature and Humidity Sensor

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


        1. devzona Автор
          12.08.2021 16:50

          Весь вопрос в сопряжение самого устройства. Как вариант можно взять устройство Tuya или Sonoff, например это. Перепрошить его на ESPHome, и подвязать к Home Assistant.


    1. Javian
      12.08.2021 16:40

      Можно декодировать датчики 433МГц. Причем они уже могут работать у соседей в метеостанциях и покупать ничего не надо.

      https://github.com/merbanan/rtl_433


      1. devzona Автор
        12.08.2021 16:55

        Зачетный проект! Но это будет температура у соседей, а не у тебя.


        1. Javian
          13.08.2021 08:16

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


          1. devzona Автор
            15.08.2021 03:05

            Это не интересно. Получается что твоя система будет зависеть от соседей, а это ненадежно. А воздух между прочем там другой, соседский).


            1. Javian
              15.08.2021 08:18

              Если это заработает. То можно и купить свой, например. Или поискать вариант с ZigBee.


  1. Mike-M
    01.11.2021 17:12

    Люблю «разжеванные» статьи, спасибо devzona.

    Есть предложение по UI:

    Если данные были получены менее 10 секунд назад, то отображается надпись — «Данные получены недавно».
    Не уверен, что, скажем, через 5 лет Вы будете помнить, что «недавно» означает «менее 10 секунд назад».
    Кроме того, другой человек может быть вообще не в курсе, что значит «недавно», и подразумевать 1 секунду, 10 секунд, минуту…
    Поэтому предлагаю указать конкретно: «Данные получены менее 10 секунд назад».


    1. devzona Автор
      02.11.2021 03:57

      Не вижу никакой необходимости писать ".. получены 4 секунды назад". Значения температуры, давления, влажности к счастью не меняются настолько часто, иначе мы просто вымерли бы как динозавры. А если данные были получены более 10 секунд назад, то отображается надпись с дискретностью в 10 секунд, а потом - 1 минута. Пользователю достаточно понаблюдать за системой и он все поймет. В крайнем случае, если не произойдет армагеддон, то всегда можно посмотреть исходники на GitHub.