• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Нужна помощь Библиотека PubSubClient

max506

New member
Пытаюсь работать с библиотекой PubSubClient, модуль ESP8266 Wemos D1.
Получаю данные от Ардуино по UART.
С интервалом 5 сек посылаю на MQTT broker Mosquitto порядка 10 метрик, используя для этого функцию client.publish(topic, value).
Столкнулся со странной особенностью, а именно если предварительно подписаться на отсылаемую тему методом client.subscribe(topic), то публикация значений в эту тему (отработка метода client.publish) происходит за в среднем 3 миллисекунды, хотя бывают и задержки. Если же не подписываться, то метод отрабатывает за 200!!! миллисекунд. Это из-за чего так? Можно ли как-нибудь ускорить работу метода публикации?
 
Последнее редактирование:

Сергей_Ф

Moderator
Команда форума
@max506 могу только предположить, что подписка устанавливает и держит соединение с сервером. Без подписки соединение переустанавливается каждую публикацию - вот и тратится время на это. Код не смотрел, могу ошибаться.
 

max506

New member
Вот код, которым я пользуюсь
Код:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>


const char *ssid = "......";  // Имя вайфай точки доступа
const char *pass = "......";  // Пароль от точки доступа

const char *mqtt_server =  "......";  // Имя сервера MQTT
const int mqtt_port = 1883; // Порт для подключения к серверу MQTT
const char *mqtt_user = "";
const char *mqtt_pass = "";

String subscr_topic = "/esp/topic1/";
String prefix = "/esp/";
String buf_recv;


WiFiClient wclient;   
PubSubClient client(wclient, mqtt_server, mqtt_port);


// Функция получения данных от MQTT брокера
void callback_subscr(const MQTT::Publish &pub)
{

  String topic = pub.topic();
  String value = pub.payload_string();


  if( topic.substring(0,subscr_topic.length()) == subscr_topic)   // передаем в UART только данные, на которые подписывались
     Serial.println(topic.substring(subscr_topic.length())+"="+value);
   
}


// Отправка данных на MQTT брокер
void Send(){

  char ch;
  String pub_topic;
  String val;
  int pos_delim;
  long t;

    // полчение данных по UART от Arduino
    while (Serial.available()) {

      ch = (char)Serial.read();
    
      if (ch == '\r') continue;

      if (ch == '\n') {
   
        // Данные приходят в виде строк в формате <тема>=<значение>
        pos_delim = buf_recv.indexOf('=');
        pub_topic = buf_recv.substring(0,pos_delim);
        val = buf_recv.substring(pos_delim+1);

        
        // #### Вызов метода публикации "виснет" на 200 миллисекунд, если предварительно не подписаться на публикуемую тему !!!!!
        t=millis();
        client.publish(prefix+pub_topic,val);
        Serial.println(millis()-t);
     
     
        buf_recv="";
        break;
      }
     
      buf_recv += ch;

    }
}


void setup() {

  Serial.begin(9600);

}

void loop() {

  // подключаемся к wi-fi
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.print(ssid);
    Serial.println("...");
    WiFi.begin(ssid, pass);

    if (WiFi.waitForConnectResult() != WL_CONNECTED)
      return;
    Serial.println("WiFi connected");
  }

  // подключаемся к MQTT серверу
  if (WiFi.status() == WL_CONNECTED) {
    if (!client.connected()) {
      Serial.println("Connecting to MQTT server");
      if ( client.connect(MQTT::Connect("ESP_Witty").set_auth(mqtt_user, mqtt_pass)) ) {
        Serial.println("Connected to MQTT server");
        client.set_callback(callback_subscr);
        client.subscribe(subscr_topic+"#"); // подписывааемся на темы с изменяемыми данными
                     
      } else {
        Serial.println("Could not connect to MQTT server");
      }
    }

    if (client.connected()){
   
      client.loop();
  
      Send();
    }
 
  }

}
 

max506

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

krepton85

Member
А может кто-нибудь код глянуть, я не очень понимаю в библиотеках классов.
Может есть возможность не подписываться на все темы и при этом публиковать сообщения, на которые не подписан, с нормальной скоростью.
Смотрю вы пользуетесь той же библиотекой pubsubclient, что и я. Этих библиотек есть 2 вида, та что мы с вами используем самая редкая, и есть др. которой все пользуются, она доступна в репазитории arduino ide методы ее использования в скетче различаются с нашей. Наша построена на объекте класса String - все текстовые строки, а та что самая популярная на строках char*.
Мною были замечены баги и в той и в той библиотеке, в нашей с вами - если в один из топиков на каторый мы подписаны быстро засыпать данные, например нажимая быстро одну и туже кнопку в приложении или дергать слайдер в приложении с которого не преривно сыпятся данные, то esp8266 виснет намертво и даже не ребутит. В др. библиотеке что есть в IDE если взять даже стандартный пример mqqt_pablish_in_callback и дописать в его с дисяток подписок, то модуль esp начинает уже виснуть на 5 подписке где-то, а далее ребутит.
 

krepton85

Member
Попробуй перейти на 2.0.0
Ок. Папробую.

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

krepton85

Member
можно ли как-нибудь ускорить работу метода публикации?
Ура, ура, меня осинило :) Наконец то понял в чем были все проблеммы с зависанием, они были как раз в этом что вы описали. На отправку одного исходящего сообщения действительно уходит много времени и если нужно подрят отправить до 10 или более исходящих сообщений то цикл void loop давольно сильно на долго преривается может на сек 2 - 3 (с 11 -ю сообщениями).
У меня в это время (раз в 5 сек) все остальные функции скетча очень сильно подвисало. И наконец то мне пришла в голову идиальная идея, как получить преривание loop длительностью всего в 1 сообщение (не важно сколько их будет).
В общем решение хитрое, нужно сделать так что бы за один цикл void loop отправлялось только одно сообщение, все последующие распределены по 1 шт, на 1 цикл void loop. В кратце: что бы отправить 10 сообщений нужно что бы цикл void loop повторился 10 раз.
С таким подходом все работает гораздо быстрей. Раз в 5 сек начинает отправлятся 10 (у меня даже 11) исходящих сообщений, за 11 циклов void loop, после чего таймер обнуляется вновь что бы вновь через 5 сек все повторилось.
 

max506

New member
нужно сделать так что бы за один цикл void loop отправлялось только одно сообщение
Ну у меня в коде так и сделано. Выгребаю из Serial порта полностью одно сообщение, отправляю, затем снова Loop и снова чтение из Serial порта.
 

krepton85

Member
@max506, ну в общем то я и др. самую знаменитую библиатеку pubsubclient точно так же победил, но только там тот же косяк был с подписками, сделал так что бы создавалась только одна подписка за цикл loop, и в общем то библиотека работает довольно таки быстро, ничего не виснет, отправляю в тэст - примере 8 исходящих сообщений (на которые сама библиотека не подписана) и все за один цикл loop.
И что нам сейчас делать? Так лень переписывать огромный скетч под др. либу. :)
 

alek2

New member
@krepton85 Тоже столкнулся с такой проблемой, 5 топиков шлет без проблем, на 6 виснет. Скетчем не поделитесь?
 

lpaha

New member
Вот код, которым я пользуюсь
Код:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>


const char *ssid = "......";  // Имя вайфай точки доступа
const char *pass = "......";  // Пароль от точки доступа

const char *mqtt_server =  "......";  // Имя сервера MQTT
const int mqtt_port = 1883; // Порт для подключения к серверу MQTT
const char *mqtt_user = "";
const char *mqtt_pass = "";

String subscr_topic = "/esp/topic1/";
String prefix = "/esp/";
String buf_recv;


WiFiClient wclient; 
PubSubClient client(wclient, mqtt_server, mqtt_port);


// Функция получения данных от MQTT брокера
void callback_subscr(const MQTT::Publish &pub)
{

  String topic = pub.topic();
  String value = pub.payload_string();


  if( topic.substring(0,subscr_topic.length()) == subscr_topic)   // передаем в UART только данные, на которые подписывались
     Serial.println(topic.substring(subscr_topic.length())+"="+value);
 
}


// Отправка данных на MQTT брокер
void Send(){

  char ch;
  String pub_topic;
  String val;
  int pos_delim;
  long t;

    // полчение данных по UART от Arduino
    while (Serial.available()) {

      ch = (char)Serial.read();
  
      if (ch == '\r') continue;

      if (ch == '\n') {
 
        // Данные приходят в виде строк в формате <тема>=<значение>
        pos_delim = buf_recv.indexOf('=');
        pub_topic = buf_recv.substring(0,pos_delim);
        val = buf_recv.substring(pos_delim+1);

      
        // #### Вызов метода публикации "виснет" на 200 миллисекунд, если предварительно не подписаться на публикуемую тему !!!!!
        t=millis();
        client.publish(prefix+pub_topic,val);
        Serial.println(millis()-t);
   
   
        buf_recv="";
        break;
      }
   
      buf_recv += ch;

    }
}


void setup() {

  Serial.begin(9600);

}

void loop() {

  // подключаемся к wi-fi
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.print(ssid);
    Serial.println("...");
    WiFi.begin(ssid, pass);

    if (WiFi.waitForConnectResult() != WL_CONNECTED)
      return;
    Serial.println("WiFi connected");
  }

  // подключаемся к MQTT серверу
  if (WiFi.status() == WL_CONNECTED) {
    if (!client.connected()) {
      Serial.println("Connecting to MQTT server");
      if ( client.connect(MQTT::Connect("ESP_Witty").set_auth(mqtt_user, mqtt_pass)) ) {
        Serial.println("Connected to MQTT server");
        client.set_callback(callback_subscr);
        client.subscribe(subscr_topic+"#"); // подписывааемся на темы с изменяемыми данными
                   
      } else {
        Serial.println("Could not connect to MQTT server");
      }
    }

    if (client.connected()){
 
      client.loop();
 
      Send();
    }
 
  }

}




А у вас есть пример скетча для ардуины, на получения данных по UART ??
 

feiiint

New member
Товарищи, помогите пожалуйста с решением задачки, у меня схожая проблема. Написал скетч который запоминает состояние после включения\выключения реле на esp8266-01S, и вносит его в EEPROM, после нештатного выключения электричества реле возвращается в исходное состояние. Т.е. после подачи питания мне нужно передать состояние на MQTT-сервер. В сущности, pubSubClientPublishing() нужно вызвать до pubSubClientSubscribe(), потому как pubSubClientSubscribe() следит за топиком и соответственно включает или выключает если в топике появляется 0 или 1. Скетч мой работает, но работает только после того как вызвана функа подписки. Пробовал делать паузу, т.к. было подозрение что публикация запускается раньше доступности сервера MQTT, потом подключил библиотеку 8266PING, после получения true на ответ сервера MQTT передает состояние в топик, и все равно не могу передать состояние (топик пустой), пока не будет выполнена подписка.
 
Сверху Снизу