Несколько лет назад я провел свои первые эксперименты с MQTT-сервером (Mosquitto), работающим на Raspberry Pi, чтобы соединить Arduino и Raspberry Pi для барабана моего сына. Полный процесс описан в моей книге Начало работы с Java на Raspberry Pi.

В этой серии статей мы собираемся применить другой подход к онлайн-сервису, совместимому с MQTT: HiveMQ Cloud. Самое большое преимущество: нет необходимости в постоянно включенном сервере, которым мы должны управлять сами. Да, Mosquitto работает на недорогом Raspberry Pi, но тем не менее нам необходимо поддерживать его работоспособность, если мы используем его в качестве центрального узла для распространения данных.

И еще одно большое преимущество: HiveMQ Cloud абсолютно бесплатно до 100 устройств! Даже для самого увлеченного производителя это много микроконтроллеров или компьютеров!

Пример кода для многих языков программирования доступен на веб-сайте HiveMQ. В этом и следующих постах мы сосредоточимся на нескольких примерах с Raspberry Pi и Java.

Настройка учетной записи HiveMQ Cloud

Начнем с нашей бесплатной учетной записи HiveMQ Cloud. Перейдите на auth.hivemq.cloudЗарегистрироваться сейчас и создайте логин. После входа в систему вам будут представлены ваши кластеры. Нажав кнопку Управление кластером, вы будете перенаправлены на представление, содержащее сведения о вашем экземпляре HiveMQ Cloud.

Перейдите в раздел «Управление доступом» и создайте имя пользователя и пароль для учетных данных, которые мы будем использовать в наших приложениях.

О малине Пи

Raspberry Pi — это не просто маленький недорогой компьютер для всех. Нет, последняя версия 4 превратилась в мощную машину, которая во многих случаях может заменить полноразмерный настольный компьютер или ноутбук.

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

В 2019 году был выпущен Raspberry Pi 4 с 64-битным четырехъядерным процессором ARM с тактовой частотой 1,5 ГГц. Эта плата доступна с 1, 2, 4 или 8 ГБ памяти. Цены варьируются от 25 до 90€.

В этой серии я буду использовать Raspberry Pi 4, но вы можете сделать то же самое с другими типами плат. Единственное, о чем вам нужно позаботиться при использовании Raspberry Pi Zero первого поколения (новая была выпущена в ноябре 2021 года) или более старых полноразмерных плат, — это использовать совместимый с ARMv6 Java JDK или среду выполнения. Дополнительная информация доступна в разделе Как установить и использовать Java 11 и JavaFX 11 на платах Raspberry Pi с процессором ARMv6.

«Волшебная часть» Raspberry Pi — это 40-контактный разъем, который позволяет подключать широкий спектр электронных компонентов, таких как светодиоды, кнопки, датчики, сервоприводы, реле, экраны… И это именно то, что мы хотим использовать в этом post для отправки данных датчиков в облако HiveMQ.

Java на Raspberry Pi

Когда вы начнете с нового Raspberry Pi, вам нужно будет записать операционную систему на SD-карту. На странице программного обеспечения веб-сайта Raspberry Pi вы можете найти инструмент Imager. С помощью этого инструмента вы можете записать одну из предопределенных версий ОС или выбрать образ, загруженный с другого сайта. Если вы выберете ОС Raspberry Pi (другое)Полная версия ОС Raspberry Pi (32-разрядная версия), у вас будет полная среда рабочего стола Linux с дополнительными инструментами программирования.

И что еще более важно для нас, Java также предустановлена! Когда вы откроете терминал и проверите версию с помощью java -versions, вы получите такой результат (в зависимости от сборки ОС Raspberry Pi):

$ java -version
openjdk version "11.0.12" 2021-07-20
OpenJDK Runtime Environment (build 11.0.12+7-post-Raspbian-2deb10u1)
OpenJDK Server VM (build 11.0.12+7-post-Raspbian-2deb10u1, mixed mode)

Это означает, что мы полностью готовы к разработке и запуску Java-приложений на нашем Raspberry Pi!

Pi4J

Проект Pi4J (начатый в 2012 году) направлен на объединение программирования на Java с электроникой. Используя Pi4J-зависимость в проекте, электронные компоненты, подключенные к контактам GPIO (ввод/вывод общего назначения) Raspberry Pi, могут управляться как объекты в Java-коде. Pi4J использует нативные библиотеки для управления GPIO, поэтому вам, как программисту, не нужно полностью осознавать всю «магию», связанную с аппаратной связью.

История

В начале 2021 года были выпущены две новые версии Pi4J:

  • Версия 1.3, выпущенная в январе 2021 года, добавила поддержку новейших плат Raspberry Pi (4, 400 и Compute 4).
  • Версия 1.4, выпущенная в марте 2021 года, ориентирована на Java 11 и лишена поддержки других плат и компонентов.

Полная история V1 доступна на сайте Pi4J.

С 2019 года велась работа над совершенно новой версией 2 Pi4J с модулями и улучшенной архитектурой. Это упростило поддержку, тестирование и выпуск проекта. В августе 2021 года был выпущен первый выпуск этой новой версии, и именно эту версию мы будем использовать здесь.

Документация

Вместе с этой версией 2 был опубликован новый веб-сайт документации, на котором можно найти много информации об электронике и о том, как использовать Java и JavaFX на Raspberry Pi.

Java-проект

Первое приложение, которое мы создадим, — это издатель данных, полные исходники доступны на GitHub. Мы будем считывать данные с разных датчиков и отправлять их значения в HiveMQ Cloud. Поскольку у каждого производителя будут свои собственные идеи и пожелания для проекта, мы не будем сосредотачиваться на компонентах и ​​проводке, а воспользуемся простым стартовым набором, который идеально подходит для этого демонстрационного проекта: CrowPi. Этот предварительно собранный комплект позволяет очень легко приступить к программированию электроники, поскольку все компоненты предварительно собраны и подключены. Так что, по крайней мере, это одна вещь, о которой вам не нужно беспокоиться или вы можете все испортить ;-).

На основе проекта CrowPi от FHNW

Студенты из Swiss FHNW University создали полноценный Java-проект для CrowPi, чтобы продемонстрировать, как всеми компонентами этого комплекта можно управлять с помощью Java. Часть их документации была переведена и доступна на веб-сайте Pi4J, оригинальную документацию на немецком языке можно найти на CrowPi Goes Java. Для каждого компонента они создали отдельное приложение, поэтому становится очень понятно, как вы можете использовать их код в качестве основы для своего собственного проекта.

Приложение для проверки концепции в этой статье использует некоторые части проекта «CrowPi Goes Java», которые были реорганизованы, чтобы вписаться в автономное приложение для одновременного считывания данных с разных датчиков или прослушивания их изменений. события и публиковать данные в HiveMQ.

Maven pom.xml

Библиотека Pi4J использует модульный подход, и при создании проекта для Raspberry Pi создается каталог со всеми необходимыми файлами. Из-за этого pom-файл может выглядеть немного перегруженным.

Зависимости содержат:

  • клиент HiveMQ MQTT для публикации данных
  • Ядро Pi4J и плагины, необходимые для этого проекта
  • SLF4J, который является структурой ведения журнала, используемой Pi4J.
  • Jackson и Jakarta.JSON для генерации данных JSON для упрощения передачи данных.

При использовании нескольких плагинов выполняются следующие шаги:

  • Скомпилируйте проект Java
  • Определить исполняемый класс
  • Создайте jar в каталоге дистрибутива
  • Скопируйте скрипт run.sh в каталог дистрибутива
  • Добавьте зависимости времени выполнения в тот же каталог

Код

Чтобы упростить понимание кода, каждая функциональность была разделена на отдельный класс. HiveMqSender.java — это основной класс, в котором все инициализируется и запускается приложение.

public class HiveMqSender {

    // Logger helper provided by Pi4J
    private static Console console;
    // Sends data to HiveMQ Cloud
    private static HiveMqManager hiveMqManager;
    // Initializes the sensors and reads the values
    private static SensorManager sensorManager;

    public static void main(String[] args) {
        console = new Console();
        hiveMqManager = new HiveMqManager(console);
        sensorManager = new SensorManager(console, hiveMqManager);

        while (true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Публикация в темах HiveMQ

В файле HiveMqManager.java мы находим весь код, необходимый для подключения к HiveMQ Cloud и публикации сообщений.

Мы используем клиент MQTT версии 5, который обеспечивает множество улучшений по сравнению с версией 3, как описано в статье Знакомство с новым протоколом MQTT 5 — MQTT 5 Essentials Part 1 и следующих статьях этой серии. И да, вы также узнаете в той статье, почему нет версии 4. :-)

Благодаря методу билдера мы можем очень легко настроить клиент и подключиться. Поскольку HiveMQ Cloud требует безопасного соединения, нам нужно использовать sslWithDefaulftConfig() и применить аутентификацию в соединении.

public class HiveMqManager {

    private static final String HIVEMQ_SERVER = "ID_OF_YOUR_INSTANCE.s1.eu.hivemq.cloud";
    private static final String HIVEMQ_USER = "YOUR_USERNAME";
    private static final String HIVEMQ_PASSWORD = "YOUR_PASSWORD";

    private static Console console;
    private static Mqtt5AsyncClient client;

    public HiveMqManager(Console console) {
        this.console = console;

        client = MqttClient.builder()
                .useMqttVersion5()
                .identifier("Java_" + UUID.randomUUID())
                .serverHost(HIVEMQ_SERVER)
                .serverPort(8883)
                .sslWithDefaultConfig()
                .buildAsync();

        client.connectWith()
                .simpleAuth()
                .username(HIVEMQ_USER)
                .password(HIVEMQ_PASSWORD.getBytes())
                .applySimpleAuth()
                .send()
                .whenComplete((connAck, throwable) -> {
                    if (throwable != null) {
                        console.println("Could not connect to HiveMQ: " + throwable.getMessage());
                    } else {
                        console.println("Connected to HiveMQ: " + connAck.getReasonCode());
                    }
                });
    }
}

Чтобы нашим датчикам было проще отправлять данные, мы расширяем этот класс с помощью метода sendMessage. Чтобы увидеть, успешно ли опубликованы наши сообщения или что пошло не так, мы используем метод whenComplete.

public void sendMessage(String topic, String message) {
    client.publishWith()
            .topic(topic)
            .payload(message.getBytes())
            .qos(MqttQos.EXACTLY_ONCE)
            .send()
            .whenComplete((mqtt5Publish, throwable) -> {
                if (throwable != null) {
                    console.println("Error while sending message: " + throwable.getMessage());
                } else {
                    console.println("Message sent to '" + topic + "': " + message);
                }
            });
}

Чтение данных с датчиков

Все датчики инициализируются в классе SensorsManager.java. Каждый датчик реализован как Component. Некоторые из них читаются с интервалом, когда SendMeasurements начинается с TimerTask. Другие отправляют изменения через слушателей, например onNoise из SoundSensor.

public class SensorManager {

    private static final String TOPIC_MOTION = "crowpi/motion";
    private static final String TOPIC_NOISE = "crowpi/noise";
    private static final String TOPIC_TOUCH = "crowpi/touch";
    private static final String TOPIC_TILT = "crowpi/tilt";
    private static final String TOPIC_SENSORS = "crowpi/sensors";

    private static final String VALUE_TRUE = "{\"value\":true}";
    private static final String VALUE_FALSE = "{\"value\":false}";

    public SensorManager(Console console, HiveMqManager hiveMqManager) {
        Context pi4j = CrowPiPlatform.buildNewContext();

        PirMotionSensorComponent motionSensor = new PirMotionSensorComponent(console, pi4j);
        motionSensor.onMovement(() -> hiveMqManager.sendMessage(TOPIC_MOTION, VALUE_TRUE));
        motionSensor.onStillstand(() -> hiveMqManager.sendMessage(TOPIC_MOTION, VALUE_FALSE));

        SoundSensorComponent soundSensor = new SoundSensorComponent(console, pi4j);
        soundSensor.onNoise(() -> hiveMqManager.sendMessage(TOPIC_NOISE, VALUE_TRUE));
        soundSensor.onSilence(() -> hiveMqManager.sendMessage(TOPIC_NOISE, VALUE_FALSE));

        TouchSensorComponent touchSensor = new TouchSensorComponent(console, pi4j);
        touchSensor.onTouch(() -> hiveMqManager.sendMessage(TOPIC_TOUCH, VALUE_TRUE));
        touchSensor.onRelease(() -> hiveMqManager.sendMessage(TOPIC_TOUCH, VALUE_FALSE));

        TiltSensorComponent tiltSensor = new TiltSensorComponent(console, pi4j);
        tiltSensor.onTiltLeft(() -> hiveMqManager.sendMessage(TOPIC_TILT, "{\"value\":\"left\"}"));
        tiltSensor.onTiltRight(() -> hiveMqManager.sendMessage(TOPIC_TILT, "{\"value\":\"right\"}"));
        tiltSensor.onShake(() -> hiveMqManager.sendMessage(TOPIC_TILT, "{\"value\":\"shaking\"}"));

        HumiTempComponent dht11 = new HumiTempComponent(console, pi4j);
        LightSensorComponent lightSensor = new LightSensorComponent(console, pi4j);
        UltrasonicDistanceSensorComponent distanceSensor = new UltrasonicDistanceSensorComponent(console, pi4j);

        Timer timer = new Timer();
        TimerTask task = new SendMeasurements(hiveMqManager, dht11, lightSensor, distanceSensor);
        timer.schedule(task, 10_000, 5_000);
    }

    private static class SendMeasurements extends TimerTask {
        private final HiveMqManager hiveMqManager;
        private final HumiTempComponent dht11;
        private final LightSensorComponent lightSensor;
        private final UltrasonicDistanceSensorComponent distanceSensor;

        public SendMeasurements(HiveMqManager hiveMqManager,
                                HumiTempComponent dht11,
                                LightSensorComponent lightSensor,
                                UltrasonicDistanceSensorComponent distanceSensor) {
            this.hiveMqManager = hiveMqManager;
            this.dht11 = dht11;
            this.lightSensor = lightSensor;
            this.distanceSensor = distanceSensor;
        }

        @Override
        public void run() {
            var sensor = new Sensor(dht11.getTemperature(), dht11.getHumidity(),
                    lightSensor.readLight(2), distanceSensor.measure());
            hiveMqManager.sendMessage(TOPIC_SENSORS, sensor.toJson());
        }
    }
}

Конфигурация Pi4J

Как вы можете видеть в предыдущем классе, контекст Pi4J инициализируется с помощью CrowPiPlatform.buildNewContext(). Pi4J использует структуру плагинов, поэтому его легко поддерживать и расширять дополнительными функциями. В большинстве случаев мы можем полагаться на Pi4J.newAutoContext() для инициализации, но поскольку нам явно нужны функциональные возможности плагина PiGpio, мы используем вспомогательный класс, чтобы убедиться, что Pi4J загружает правильные плагины. PiGpio — это собственная библиотека (написанная на C), которая используется Pi4J «под капотом» для обработки GPIO.

public static Context buildNewContext() {
    // Initialize PiGPIO
    var piGpio = PiGpio.newNativeInstance();

    // Build Pi4J context with this platform and PiGPIO providers
    return Pi4J.newContextBuilder()
            .noAutoDetect()
            .add(new CrowPiPlatform())
            .add(
                    PiGpioDigitalInputProvider.newInstance(piGpio),
                    PiGpioDigitalOutputProvider.newInstance(piGpio),
                    PiGpioPwmProvider.newInstance(piGpio),
                    PiGpioI2CProvider.newInstance(piGpio),
                    PiGpioSerialProvider.newInstance(piGpio),
                    PiGpioSpiProvider.newInstance(piGpio)
            )
            .build();
}

Код датчика

Взгляните на полные исходники этого проекта или проект Pi4J CrowPi, если вы ищете конкретный датчик.

В этом посте мы рассмотрим один из компонентов: датчик звука. Это простой датчик DigitalInput, и весь его код реализован в SoundSensorComponent.java. Нам нужно только реализовать методы для обработки изменения вывода между низким (без звука) и высоким (обнаружен звук).

Пин инициализируется следующей частью кода:

protected DigitalInputConfig buildDigitalInputConfig(Context pi4j, int address, long debounce) {
    return DigitalInput.newConfigBuilder(pi4j)
            .address(address)
            .id("BCM" + address)
            .name("SoundSensor")
            .pull(PullResistance.PULL_UP)
            .debounce(debounce)
            .build();
}

Pi4J также предоставляет шаблон построителя для настройки GPIO. Для этого датчика DigitalInput нам нужно настроить:

  • адрес: контакт, к которому он подключен = BCM 24 на CrowPi
  • уникальный идентификатор, поэтому Pi4J может обрабатывать его в течение всего срока службы
  • имя: для нашей собственной справки, например. в журнале
  • потяните вверх или вниз: как нужно обрабатывать изменения состояния
  • debounce: интервал между изменениями состояния, чтобы избежать слишком быстрого «пинг-понга» между изменениями состояния

ОС CrowPi

Вы можете запустить этот проект на любой ОС Raspberry Pi с Java, но когда вы запускаете его на CrowPi, вы можете получить кикстарт, используя CrowPi OS, которая является частью проекта Pi4J (опять же, благодаря FHNW University ).

Некоторым датчикам (расстояние и влажность) в CrowPi требуется очень строгое время для получения надежных результатов данных. Этого очень трудно добиться в системе Linux, как с Python, так и с Java. В Linux возможны некоторые настройки для улучшения результатов, и они предоставляются по умолчанию в CrowPi Os, основанной на ОС Raspberry Pi, но с некоторыми дополнительными улучшениями для CrowPi.

Но даже с этими настройками результаты не на 100% надежны. Датчики такого типа лучше подходят для микроконтроллеров, таких как Arduino или Raspberry Pi Pico. Также можно использовать операционную систему реального времени, как описано в Операционная система реального времени Raspberry Pi (RTOS): какую выбрать Чери Тан.

И последнее замечание: в этом проекте используется датчик наклона, который необходимо включить с помощью микропереключателя на плате CrowPi:

Сборка и запуск на Raspberry Pi

Вы можете разработать Java-проект на своем ПК и скопировать jar-файлы на Raspberry Pi. Или скопируйте исходники и скомпилируйте на плате. Или, конечно, разрабатывать на самой Raspberry Pi (например, с помощью Visual Studio Code). У каждого есть свои плюсы и минусы. На Pi4J-веб-сайте в разделе «Начало работы» для каждого подхода представлена ​​дополнительная информация.

Если вы просто хотите запустить это примерное приложение, самый простой способ, вероятно, — установить Maven, загрузить исходные коды, собрать и запустить на самой Raspberry Pi. Если вы хотите воспользоваться этим кратким руководством, вы можете клонировать код и запустить его в несколько строк:

$ sudo apt install maven 
$ git clone https://github.com/FDelporte/HiveMQ-examples.git 
$ cd HiveMQ-examples/java-to-hivemq 
$ mvn package 
$ cd target/distribution 
$ sudo bash run.sh

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

Message sent to 'crowpi/sensors': {"temperature":12.0,"humidity":154.0,"light":170.0,"distance":159.57}
Message sent to 'crowpi/sensors': {"temperature":12.0,"humidity":154.0,"light":169.16666666666669,"distance":168.88}
Message sent to 'crowpi/motion': {"value":true}
Message sent to 'crowpi/sensors': {"temperature":12.0,"humidity":154.0,"light":169.16666666666669,"distance":167.44}
Message sent to 'crowpi/motion': {"value":false}
Message sent to 'crowpi/sensors': {"temperature":12.0,"humidity":154.0,"light":163.33333333333334,"distance":167.77}
Message sent to 'crowpi/noise': {"value":false}
Message sent to 'crowpi/noise': {"value":true}

Тестовая страница WebSocket

Журнал нашего приложения показывает, что данные публикуются в облаке HiveMQ, но мы хотим быть уверены, прежде чем продолжить создание клиента… К счастью, есть клиент веб-сокета, который мы можем очень легко использовать для этого теста! Перейдите на hivemq.com/demos/websocket-client/ и заполните все поля своими учетными данными. Обязательно установите флажок SSL! Когда все заполнено правильно и вы нажмете кнопку Connect, вы увидите зеленую индикацию connected сверху.

Теперь мы можем определить темы, на которые мы хотим подписаться, например. «crowpi/motion», «crowpi/sensors»… Как только сообщения отправляются с нашего Raspberry Pi, они отображаются на этой тестовой веб-странице.

Большой! У нас есть данные, которые передаются с нашего Raspberry Pi в облако HiveMQ!!!

Заключение

Как всегда, когда вы комбинируете Java с Maven, такой проект можно реализовать с минимальным кодом, но при этом он будет простым для понимания и управления. Pi4J добавляет необходимые инструменты, чтобы скрыть сложность работы с GPIO, и «преобразует» электронные компоненты в объекты Java.

Благодаря облачной службе HiveMQ мы можем бесплатно публиковать данные до 100 устройств в постоянно включенном, не требующем обслуживания брокере сообщений! В сочетании с недорогим Raspberry Pi перед всеми производителями открывается целый мир возможностей.

В следующей статье этой серии мы собираемся визуализировать данные наших датчиков на приборной панели. И, конечно же, снова с Java (и JavaFX) на Raspberry Pi!

Это сообщение было написано по запросу HiveMQ и первоначально было опубликовано в Блоге HiveMQ.