Janus MQTT Proxy - один из моих любимых проектов. Это служба, которая подключается к брокеру MQTT и подписывается на все события, в то время как клиенты подключаются к прокси-серверу и взаимодействуют с ним, как если бы это был настоящий брокер MQTT.

Прокси-сервер может:

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

Зачем это нужно

MQTT не имеет стандарта для структуры темы и полезной нагрузки сообщения. Например, в некоторых сервисах вам нужно отправить 1 в тему «/ my / lamp / on», чтобы включить лампу, но в некоторых других вам нужно отправить «Вкл» в тему «/ my / lamp». Если вам нужно связать эти две службы вместе, вам придется явно настроить одну из них, чтобы отправлять правильные сообщения в правильную тему. Если нужно настроить много тем, ваша конфигурация станет огромной и абсолютно нечитаемой.

Janus MQTT позволяет вам оставить сервисы такими, какие они есть: каждый работает с темами по своему усмотрению, а вся конфигурация преобразований находится в одном месте. Чем больше у вас разных сервисов и устройств, тем удобнее становится такой подход.

В идеале каждая служба должна брать свою конфигурацию из структуры темы. Например, если есть тема «/ light / main», тогда служба должна понимать, что она управляет основным светом. К сожалению, таких умных сервисов я еще не видел.

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

Конфигурация Janus MQTT

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

Чтобы было понятно, я опишу небольшую часть моей конфигурации, которая управляет светом:

broker_to_client:
  # MQTT discovery devices specification.
  - topic: ^/devices/wb-gpio/controls/LIGHT_([^/]*)/meta/type$
    template: /homeassistant/light/{{.f1}}/config
    val_map:
      switch: >-
        {
        "command_topic":"/light/{{.f1}}/state",
        "state_topic":"/light/{{.f1}}/state",
        "name":"{{.f1}}"
        }
  - topic: ^/devices/wb-gpio/controls/LIGHT_([^/]*)$
    template: /light/{{.f1}}/state
    val_map: {0: OFF, 1: ON}
client_to_broker:
  - topic: ^/light/([^/]*)/state$
    template: /devices/wb-gpio/controls/LIGHT_{{.f1}}/on
    val_map: {OFF: 0, ON: 1}

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

Интересно, что в этой конфигурации мой Home Assistant использует одну тему для изменения и чтения состояния освещения. Затем эта тема разделена на две части внутри прокси.

Раздел MQTT Discovery на самом деле является взломом. Он заменяет специальные разделы, которые обычно используются в стандартном интерфейсе управления моего контроллера. У каждого реле в моей системе есть эта тема с «переключателем». Все эти сообщения имеют флаг «сохранено» и отправляются сразу после подключения к брокеру. Я просто заменяю все эти сообщения JSON-структурами, используемыми Home Assistant для настройки устройств.

Janus MQTT Внутреннее устройство

Сервис сделан на Golang и использует библиотеку paho.mqtt.golang. Эта библиотека реализует клиент MQTT и не предназначена для использования в качестве сервера, поэтому мне пришлось создать сервер самостоятельно, используя клиентские части MQTT.

Основной принцип работы очевиден: Janus MQTT выдает себя за брокера, он принимает сообщения от клиентов, трансформирует их и отправляет реальному брокеру. То же верно и в обратном направлении. Другими словами, Янус MQTT - это человек посередине.

Самое интересное об услуге:

  • Поддержка разных уровней QoS. Уровни 1 и 2 требуют подтверждения доставки сообщения, которое на самом деле является диалогом между клиентом и сервером. Каждое отправленное сообщение запускает новый диалог подтверждения доставки;
  • Эти диалоги требуют, чтобы сообщения имели идентификаторы сообщений, закодированные в uint16, для этого необходимо реализовать специальный алгоритм для повторного использования идентификатора;
  • Janus MQTT поддерживает одно соединение с брокером и подписан на все сообщения. Каждое полученное сообщение затем повторно отправляется всем клиентам, подписки которых соответствуют этому сообщению;
  • Я не хотел создавать хранилище сообщений внутри службы, но мне пришлось создать хранилище в памяти для сохраненных сообщений.

Обработка MQTT-пакетов от клиентов описана в функции client.serveIncoming. Он работает в горутине и читает пакеты из сокета TCP. Эта функция содержит высокоуровневую логику обработки пакетов MQTT.

  • ConnectPacket - аутентифицировать пользователя и вернуть упакованный Connack с подтверждением или ошибкой;
  • SubscribePacket - отправьте Suback для подтверждения подписки и отправки сохраненных сообщений клиенту;
  • UnsubscribePacket - отправить Unsuback для подтверждения отмены подписки и отмены подписки;
  • PingreqPacket - отправить Pingresp обратно;
  • PublishPacket - запустить автомат отправки сообщений от клиента к брокеру;
  • PubackPacket, PubrelPacket, PubcompPacket - эти сообщения проксируются в соответствующие автоматы отправки сообщений.
  • DisconnectPacket - отключить.

Нам нужны автоматы отправки клиентских сообщений и брокеры, чтобы поддерживать разные уровни QoS. В MQTT есть три уровня QoS:

  • QoS 0: отправка без подтверждения доставки;
  • QoS 1: отправка с подтверждением доставки;
  • QoS 2: отправка с однократным подтверждением доставки;

Я думаю, что QoS 1 и особенно QoS 2 не имеют большого смысла дома, но было интересно их реализовать (уровень 2 еще не полностью реализован, поэтому все QoS ограничены уровнем 1, протокол MQTT позволяет это)

QoS = 2 от клиента к брокеру. Обработка пакетов публикации:

QoS = 2, от брокера к клиенту. Обработка пакетов публикации:

Как я его использую

Я использую Janus MQTT для организации взаимодействия между этими тремя компонентами:

  • Wiren Board - главный контроллер умного дома;
  • Home Assistant - фронтенд (есть удобное приложение);
  • Яндекс2mqtt - голосовое управление.

Wiren Board

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

Яндекс2mqtt

Реализует шлюз для платформы Yandex IoT. Я могу сказать «Алиса, включи свет» (по-русски), и голосовой помощник Яндекса отправит эту команду в облако Яндекса, где их IoT-платформа распознает текст и перенаправит команду на мой шлюз. Шлюз отправит сообщение брокеру MQTT, и загорится индикатор.

На данный момент в конфиге Яндекс2mqtt есть только один светильник, но самый главный.

$ mosquitto_sub -h localhost -u yandex2mqtt -P yandex2mqtt -t ‘#’ -v
/light/LIVING_TABLE/state 1

Домашний помощник

Это интерфейс, который может управлять освещением и обогревом. Он собирает статистику и рисует графики.

Я не хочу тратить свое время на настройку Home Assistant. К счастью, в Home Assistant есть MQTT Discovery. Это особый режим, когда Home Assistant получает конфигурацию из самого MQTT. Мне просто нужно было создать специальные разделы со структурами JSON в них, которые описывают мои устройства.

В конце концов, моя конфигурация Home Assistant очень проста:

mqtt:
  username: !env_var MQTT_USER
  password: !env_var MQTT_PASS
  broker: !env_var MQTT_HOST
  discovery: true
  discovery_prefix: /homeassistant

Янус MQTT

Конфигурация Janus MQTT для Home Assistant - самая сложная. Он состоит всего из 100 строк, но описывает: 17 групп освещения, 6 термостатов с контролем температуры в помещении и 5 обогревателей теплого пола. Весь конфиг доступен здесь.

Докер

Все службы работают в Docker на плате NanoPi и описаны в docker-compose.yml.

Наконец-то

Моя установка с Janus MQTT работает уже шесть месяцев. Было несколько мелких ошибок, которые я исправил, но в целом все нормально.

Образ Janus MQTT занимает около 10 Мбайт благодаря Golang.

Эта статья была впервые опубликована на русском языке на сайте habr.com. После этой публикации мой проект собрал 15 звезд на github, и несколько человек, о которых я знаю, использовали этот сервис в своих настройках. Я думаю, что проект может быть интересен и англоязычной аудитории, поэтому, пожалуйста, попробуйте его и дайте мне обратную связь.

Вы можете скачать / посмотреть сервис здесь:

Https://github.com/phoenix-mstu/janus-mqtt-proxy

Мой основной файл для создания докеров находится здесь:

Https://github.com/phoenix-mstu/smart_home/tree/master/raspberry