Лирическое отступление: когда-то при переходе с Windows на UNIX для меня стал открытием тот факт, что файловая система в UNIX намного более стандартизирована и упорядочена, чем в Windows (особенно в Windows того времени).

В самом деле, если в Windows было в порядке вещей свалить в один каталог саму программу, ее настройки, вспомогательные файлы, и всё это положить куда угодно как попало - то в UNIX была определенная иерархия файлов, что где должно лежать, чтобы было по фен-шую: bin, lib, var, etc...

Казалось бы, при чем тут MQTT и наши дни? При том, что при попытке организовать взаимодействие между устройствами при помощи MQTT сообщений я увидел тот же самый бардак, какой был когда-то в Windows: одна система отправляет сообщения как zigbee/#, другая как mesh/#, третья еще как-то, все самодельные устройства могут отправлять вообще любые топики, и если потом требуется связать одно с другим - то нужно помнить что на что реагирует и как что рассылает.

И чтобы превратить бардак в порядок - придумал сам для себя некую стандартизацию сообщений.
Например, если происходит какое-то событие - это топик event/XXX, если событие требует реакции - это alarm/XXX, если нужно отправить команду - command/XXX.
Просто сообщения, например, текущий статус, etc/XXX.

И оказалось, что такая мелочь сильно упрощает жизнь: скажем, если в ответ на событие "пропало напряжение" нужно отправить в телеграм текст - можно, конечно, написать отдельный обработчик для этого, и еще для 100500 событий.
А можно написать обработчик, который при появлении etc-сообщения от устройства контроля напряжения просто сформирует alarm/message с нужным текстом, и уже другой обработчик, слушающий конкретно alarm/message - отправит текст куда надо.

При этом блок контроля напряжения не заботится о способе доставки, скрипт доставки доставит любой текст, не в телеграм так в СМС, остается сделать скрипт, который отслеживает жалобу блока контроля и отправляет alarm.
Для одной задачи это конечно избыточно, но если подключить к той же схеме "контроль температуры", "контроль протечки", "контроль открытия ворот", "контроль срабатывания замка" - мы просто отслеживаем разные etc-сообщения и формируем alarm с разным текстом, всё остальное происходит само.

Если надо включить свет - отправим не alarm, а command/XXX - и реле, которое слушает конкретно этот command, сработает, хоть одно, хоть 10.
Если надо именить настройки котла отопления - достаточно просто отправить соответствующий command, который слушает именно котел - и снова всё происходит как бы само.

Причем даже неважно, в какой сети работает котел: простой WiFi, Zigbee или PainlessMesh - достаточно лишь запустить скрипт-драйвер, который преобразует, скажем, command/heat1 в mesh/to/141455135 или в команды Zigbee-протокола.

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

Ну а реализация всего этого очень простая: любое самодельное устройство всегда можно заставить реагировать на свою команду и отправлять свое сообщение так как требуется, а интерфейс к "чужим" и скрипты автоматизации прекрасно пишутся всё на том же Perl:

#!/usr/bin/perl -w

$|=1;

use Net::MQTT::Simple;
use JSON;
use Data::Dumper;

$SIG{CHLD} = "IGNORE";

########################################

my $mqtt = Net::MQTT::Simple->new("127.0.0.1");

# send MQTT message
sub pub {
 my ($topic, $message, $retain) = @_;
 my $pid = fork();
 if(defined $pid && $pid == 0){
   if($retain){
     $mqtt->retain($topic => $message);
   }else{
     $mqtt->publish($topic => $message);
   }
   sleep(2);
   exit(0);
 }
}

########################################
# MAC термодатчика на складе
my $term = '287d92440600144a7';
# температура по умолчанию
my $t0 = 7;

# processing
$mqtt->run(
  'etc/stock1/control' => sub {
    my ($topic, $message) = @_;
    my $data = undef;

    if($message =~ /^{.*}$/){
      $data = from_json($message);
    }

    if(defined $data && defined $data->{ $term }){

      my $t = $data->{$term};

      my $m = {
        dtm         => $data->{dtm} || time,
        temperature => $data->{ $term },
      };
      pub("state/temperature/stock1", to_json($m), 1);

      if($t > $t0){
        pub("command/stock1/control","off 1");
      }
      elsif($t < $t0){
        pub("command/stock1/control","on 1" );
      }

    }
  },
  'command/stock1/settemp' => sub {
    my ($topic, $message) = @_;
    my $data = undef;

    if($message =~ /^{.*}$/){
      $data = from_json($message);
    }

    if(defined $data && defined $data->{ temperature }){
      $t0 = $data->{ temperature };
    }

  },
  #===============================
);

exit;

Этот скрипт просто слушает сообщения от блока контроля на складе, и занимается тем что включает или выключает обогреватель, поддерживая температуру 7 градусов.
При этом текущая температура помещается в retain-топик state/temperature/stock1, откуда ее можно всегда узнать, а команда command/stock1/settemp позволяет установить температуру, которую будет поддерживать автоматика.

Сам блок контроля умеет только отправлять температуру и включать-выключать реле (ну есть еще встроенная защита, температура не может быть выше 40, но это тут не важно).
Его можно переделать на Zigbee, нужно будет только дописать пару правил в скриптах Tasmota, не меняя всего остального.

Для запуска этого и подобных скриптов прекрасно подходит коробочка-TV-box: много памяти не требуется, производительности тоже, питание 5В через адаптер от аккумулятора, управление удаленное...

В эту схему не вписывается HomeAssistant — ну так у меня его и нет, не проблема.

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


  1. olku
    25.01.2025 06:22

    Не подскажете что почитать про протоколы умных счётчиков потребления газа, электроэнергии, тепла, и организации связи с ними?


    1. JBFW Автор
      25.01.2025 06:22

      Поискать в документации производителя?

      С электричеством проще всего, у большинства счётчиков есть выход-контакт, замыкается скажем 1 раз на каждый 1 Вт*ч - ставим счётчик импульсов, считаем, передаем по mqtt.

      Есть водяные счётчики по такой же схеме, наверно газовые тоже, этого не знаю


      1. Telmah
        25.01.2025 06:22

        В газовых геркончики вешают, а в нутре магнитик крутится, принцип тоже - считаем замыкания


      1. olku
        25.01.2025 06:22

        Беглый поиск выдает IEC 62056, ANSI C12.18, OSGP, DLMS, COSEM, IDIS. Складывается впечатление что вендоры в погоне за вендор-локами смешивают сетевые и прикладные протоколы с интерфейсами. Хотелось бы систематизировать многообразие smart meters.


  1. xSVPx
    25.01.2025 06:22

    Можно группировать по типу, как делаете вы, а можно по источнику, как делают многие.

    Т.е. ветки имеют вид типа /место/устройство/данные или команды.

    И совершенно не факт, что ваш подход лучше. Вы группируете значения по типу, но обычно надо знать какие значения есть для конкретного датчика.


    1. JBFW Автор
      25.01.2025 06:22

      Ну я-то под себя делаю )

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

      Управление даёт команду "включи" - а кто конкретно и как будет это выполнять решает "драйвер", который как раз и формирует специальную команду под конкретный девайс.

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

      Что-то типа HAL - абстрагируемся от железа. В случае замены - просто меняется что-то в драйвере, не затрагивая остального.

      Ещё пример: включение ночного света: драйвер знает, что на вот такие и такие реле надо отправить такую команду, на такой диммер - другую, на ещё один - третью. А вот саму команду включить или выключить - можно отправить хоть кнопкой, хоть автоматически по реле или по таймеру, разницы нет.


  1. past
    25.01.2025 06:22

    Есть вот такая конвенция на этот счёт. Там не только про mqtt

    https://homieiot.github.io/