Ардуино нано, mqtt, много топиков

Тема в разделе "Arduino & Shields", создана пользователем yden, 18 дек 2018.

  1. yden

    yden Гик

    Добрый.
    Подскажите плиз. Есть ардуино нано, w5100 - работает климат-контролем в доме. Ардуинка шлет контроллеру данные о климате по mqtt, получает от него команды.
    Суть проблемы: если ардуинка подписана\публикует много топиков, то часты зависания mqtt. Ардуинка с одним топиком работает месяцами без сбоев.
    Может где я в коде напартачил.
    Код:
    Код (C++):
    #include <OneWire.h>
    #include <DallasTemperature.h>
    #include <DHT.h>
    #include <Wire.h>
    #include <SPI.h>
    #include <PubSubClient.h>
    #include <Ethernet2.h>
    #include <Bounce.h>

    // =======================================================================
    // Конфигурация устройства MQTT:
    byte mac[] = { 0xDE, 0xAD, 0xBE, 0xE8, 0xFE, 0xE7 };
    IPAddress ip(192, 168, 1, 77);
    IPAddress dnServer(192, 168, 1, 1);
    IPAddress gateway(192, 168, 1, 1);
    IPAddress subnet(255, 255, 255, 0);

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

    EthernetClient ethClient;
    PubSubClient client(ethClient);

    // =======================================================================

    //ds18b20
    OneWire oneWire(5); // вход датчиков 18b20
    DallasTemperature ds(&oneWire);
    DeviceAddress ds_budka = {0x28, 0x53, 0xD9, 0x1E, 0x00, 0x00, 0x80, 0x65};
    //2853D91E00008065

    //подсчет времени
    //опрос датчиков
    long previousMillis_sensor = 0;  // храним время последнего опроса датчиков
    long interval_sensor = 30000;     //интервал

    //mqtt
    long previousMillis_mqtt_send = 0;  // храним время последнего подключения
    long interval_mqtt_send = 15000;     //интервал

    //mqtt broker
    long previousMillis_mqtt_connect = 0;  // храним время последнего подключения
    long interval_mqtt_connect = 60000;     //интервал
    unsigned long currentMillis_mqtt_connect = 0;


    int t_kux;
    int h_kux;
    int t_kux_tmp = 0;
    int h_kux_tmp = 0;

    int t_san;
    int h_san;
    int t_san_tmp = 0;
    int h_san_tmp = 0;

    int t_budka;
    int t_budka_tmp = 0;

    byte i = 0;

    //геркон крышки унитаза
    const byte inGorshok = 2; //вход крышка унитаза
    //#define outSirena A6 //выход сирена

    //создаем объект класса Bounce. Указываем пин, к которому подключена кнопка, и время дребезга в мс.
    Bounce bouncer1 = Bounce(inGorshok, 50);

    boolean flag_karlson_san = false; //флаг карлсона санузел
    boolean flag_karlson_kux = false; //флаг карлсона кухня
    boolean flag_gorshok = false; //флаг сирены крышки горшка
    boolean flag_budka = false; //флаг отопление будка
    boolean flag_heating_kux = false; //флаг отопление кухня

    //вход для dht
    const byte DHTPIN2 = 4; // датчик санузел
    const byte DHTPIN3 = 3; // датчик кухня

    #define DHTTYPE_2301 DHT21   // DHT 21 (AM2301) кухня + улица
    #define DHTTYPE_2302 DHT22   // DHT 22 (AM2302) санузел

    //выходы вентиляторов
    const byte outPin1 = 6; // кухня
    const byte outPin2 = 7; // санузел
    const byte outPin3 = 8; // будка
    const byte outSirena = 9; // сирена

    //выходы отопление кухня
    const byte outPin4 = A4; // кухня

    DHT dht2(DHTPIN2, DHTTYPE_2302);
    DHT dht3(DHTPIN3, DHTTYPE_2301);

    // =======================================================================
    // Функция получения данных от сервера
    void callback(char* topic, byte* payload, unsigned int length)
    {
      //--------------------------------------------------------------------------------------
      if (String(topic) == "ihouse/climat/san/karlson")
      {
        if ((char)payload[0] == '0') flag_karlson_san = true;
        if ((char)payload[0] == '1') flag_karlson_san = false;

        digitalWrite(outPin2, flag_karlson_san);
      }

      if (String(topic) == "ihouse/climat/kux/karlson")
      {
        if ((char)payload[0] == '0') flag_karlson_kux = true;
        if ((char)payload[0] == '1') flag_karlson_kux = false;

        digitalWrite(outPin1, flag_karlson_kux);
      }

      if (String(topic) == "ihouse/climat/budka/heating")
      {
        if ((char)payload[0] == '0') flag_budka = true;
        if ((char)payload[0] == '1') flag_budka = false;

        digitalWrite(outPin3, flag_budka);
      }

      if (String(topic) == "ihouse/climat/kux/heating")
      {
        if ((char)payload[0] == '0') flag_heating_kux = true;
        if ((char)payload[0] == '1') flag_heating_kux = false;

        digitalWrite(outPin4, flag_heating_kux);
      }

      if (String(topic) == "ihouse/gadget/gorshok")
      {
        if ((char)payload[0] == '1') flag_gorshok = true;
        if ((char)payload[0] == '0') flag_gorshok = false;
      }
    }

    // =======================================================================
    void mqtt_reconnect()
    {
      unsigned long currentMillis_mqtt_connect = millis();

      // подключаемся к MQTT серверу
      if (currentMillis_mqtt_connect - previousMillis_mqtt_connect >= interval_mqtt_connect)
      {
        String clientId = "nanoclimat-";
        clientId += String(random(0xffff), HEX);
        if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass))
        {
          client.publish("ihouse/climat/kux/karlson", String(flag_karlson_kux).c_str());
          client.publish("ihouse/climat/san/karlson", String(flag_karlson_san).c_str());
          client.publish("ihouse/climat/budka/heating", String(flag_budka).c_str());
          client.publish("ihouse/climat/kux/heating", String(flag_heating_kux).c_str());

          //подписываемся по топики
          client.subscribe("ihouse/climat/kux/karlson");
          client.loop();
          client.subscribe("ihouse/climat/san/karlson");
          client.loop();
          client.subscribe("ihouse/climat/budka/heating");
          client.loop();
          client.subscribe("ihouse/climat/kux/heating");
          client.loop();
          client.subscribe("ihouse/gadget/gorshok");
          client.loop();
        }
        previousMillis_mqtt_connect = currentMillis_mqtt_connect;
      }
    }

    // =======================================================================
    void setup()
    {
      client.setServer(mqtt_server, mqtt_port);
      client.setCallback(callback);
      delay(10);

      Ethernet.begin(mac, ip, dnServer, gateway, subnet);
      delay(10);

      dht2.begin();
      dht3.begin();
      ds.begin();

      //для релюшек
      pinMode(outPin1, OUTPUT);
      digitalWrite(outPin1, HIGH);

      pinMode(outPin2, OUTPUT);
      digitalWrite(outPin2, HIGH);

      pinMode(outPin3, OUTPUT);
      digitalWrite(outPin3, HIGH);

      pinMode(outPin4, OUTPUT);
      digitalWrite(outPin4, HIGH);

      //геркон горшка
      pinMode(inGorshok, INPUT);

      //сирена горшка
      pinMode(outSirena, OUTPUT);
      digitalWrite(outSirena, LOW);

    }

    // =======================================================================
    // Функция отправки в топик
    void MQTT_Send()
    {
      unsigned long currentMillis_mqtt_send = millis();

      if (currentMillis_mqtt_send - previousMillis_mqtt_send >= interval_mqtt_send)
      {
        previousMillis_mqtt_send = currentMillis_mqtt_send;
        client.publish("ihouse/work/climat", String(random(1000)).c_str());
      }
      delay(10);
    }

    // =======================================================================
    void loop()
    {
      unsigned long currentMillis_sensor = millis();

      //--------------------------------------------------------------------------------------
      if (!client.connected())
      {
        mqtt_reconnect();
      }

      client.loop();
      MQTT_Send();

      //--------------------------------------------------------------------------------------
      //проверка состояния геркона - санузел
      if ( bouncer1.update() )
      {
        //если считано значение 1
        if (bouncer1.read() == HIGH)
        {
          client.publish("ihouse/climat/san/gorshok", String(1).c_str());
        }
        else
        {
          client.publish("ihouse/climat/san/gorshok", String(0).c_str());
        }
      }

      //--------------------------------------------------------------------------------------
      //проверка статуса крышки горшка
      if (flag_gorshok == true)
      {
        digitalWrite(outSirena, HIGH);
      }
      else
      {
        digitalWrite(outSirena, LOW);
      }

      //--------------------------------------------------------------------------------------
      //влажность+температура+давление
      if (currentMillis_sensor - previousMillis_sensor > interval_sensor)
      {
        previousMillis_sensor = currentMillis_sensor;
        i++;
      }

      //санузел
      if (i == 1)
      {
        if (dht2.readHumidity() <= 100) h_san = dht2.readHumidity();
        if (dht2.readTemperature() <= 50) t_san = dht2.readTemperature();

        if (t_san != t_san_tmp)
        {
          client.publish("ihouse/climat/san/temp", String(t_san).c_str());
          t_san_tmp = t_san;
        }
        if (h_san != h_san_tmp)
        {
          client.publish("ihouse/climat/san/hum", String(h_san).c_str());
          h_san_tmp = h_san;
        }
      }

      //кухня
      if (i == 2)
      {
        if (dht3.readHumidity() <= 100) h_kux = dht3.readHumidity();
        if (dht3.readTemperature() <= 50) t_kux = dht3.readTemperature();

        if (t_kux != t_kux_tmp)
        {
          client.publish("ihouse/climat/kux/temp", String(t_kux).c_str());
          t_kux_tmp = t_kux;
        }
        if (h_kux != h_kux_tmp)
        {
          client.publish("ihouse/climat/kux/hum", String(h_kux).c_str());
          h_kux_tmp = h_kux;
        }
      }

      //dallas подготовка
      if (i == 3)
      {
        ds.requestTemperatures(); // считываем температуру с датчиков
      }

      //dallas считывание
      if (i == 4)
      {
        if (ds.getTempC(ds_budka) <= 50) t_budka = ds.getTempC(ds_budka);

        if (t_budka != t_budka_tmp)
        {
          client.publish("ihouse/climat/budka/temp", String(t_budka).c_str());
          t_budka_tmp = t_budka;
        }

        i = 0;
      }
    }
     
    благодарю
     
  2. SergeiL

    SergeiL Оракул Модератор

    Посмотрел свой дачный, подвальный, контроллер на Leonardo ETH, там правда W5500 и Ethernet2.
    Там публикуется 8 топиков и подписывается на 9 топиков.
    Не замечал проблем с ним, но я не выводил с него счетчика переподключений по MQTT.

    А что значит зависания MQTT?

    По коду не заметил чего-то сильно неправильного, но я бы убрал совсем работу со string.
    Не нужны там String. я использую const char * и char *

    Ну еще может быть вынес бы публикацию из mqtt реконнекта, заменив на взвод флага, а саму публикацию поставил бы в loop, при условии, что флаг взведен.

    У меня сделано так:

    Публикация:
    Код (C++):
    const char *Mqtt_Pub_Str[] = {"/mh/ds/bs/leo/starts",
                                  "/mh/ds/bs/leo/restarts",
                                  "/mh/ds/bs/leo/lte_reset",
                                  "/mh/ds/bs/leo/vcc",
                                  "/mh/ds/bs/leo/bat1u",
                                  "/mh/ds/bs/leo/bat2u",
                                  "/mh/ds/bs/leo/bat1i",
                                  "/mh/ds/bs/leo/bat2i"
                                 };

    byte          Mqtt_Update_flag = 0x07;     //  bit 0 -  StartNumber. 1- SelfReStartNumber; 3- LTE_ReStartNumber
    byte          Mqtt_Update_counter = 180;  //
    byte          Main_Data_byte[EEPROM_DATA_LENG];

    void mqtt_publish_changes()
    {

      char buff[30];
      byte i;

      if (Mqtt_Update_flag)
      {
        for (i = 0; i < 3; i++)
        {
          if ( Mqtt_Update_flag & (1 << i))
          {
            itoa((int) Main_Data_byte[i], buff, 10);
            client.publish(Mqtt_Pub_Str[i], buff);
            Mqtt_Update_counter = 180;
            Mqtt_Update_flag &= ~(1 << i);
            return;
          }
        }
     
        for (i = 0; i < 5; i++)
        {
          if ( Mqtt_Update_flag & (1 << (3 + i)))
          {
            if (i == 3 || i == 4 )
              sprintf(buff, "%d.%02d", ADC_Val[i] / 100, ADC_Val[i] % 100); // выведем ток с двумя знаками после запятой
            else
              sprintf(buff, "%d.%d", ADC_Val[i] / 10, ADC_Val[i] % 10);    // выведем напряжение с одним знаком после запятой
            client.publish(Mqtt_Pub_Str[3 + i], buff);
            Mqtt_Update_counter = 180;
            Mqtt_Update_flag &= ~(1 << (3 + i));

            return;
          }
        }
     
      }

    }
     
    Каждый вызов из loop() публикуется только одно значение и снимается флаг (соответствующий бит флага изменений).
    В следующий вызов, будет опубликовано следующее значение, и будет снят следующий бит, соответствующий публикуемому значению.

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

    Получение:
    Код (C++):
    #define MQTT_NUM_SUB_TOP 9

    typedef struct _mqtt_subscribe_struct
    {
      const char  *Mqtt_Sub_String;
      void        (*function)(byte index, byte param);
    } MQTT_Subscibe_Struct;


    MQTT_Subscibe_Struct  MQTT_Subsc_Data[] = {
      {"/mh/ds/bs/leo/count_res",    &reset_counters },
      {"/mh/ds/bs/leo/rel_00",       &rel_switch},
      {"/mh/ds/bs/leo/rel_01",       &rel_switch},
      {"/mh/ds/bs/leo/rel_02",       &rel_switch},
      {"/mh/ds/bs/leo/rel_03",       &rel_switch},
      {"/mh/ds/bs/leo/rel_10",       &rel_switch},
      {"/mh/ds/bs/leo/rel_11",       &rel_switch},
      {"/mh/ds/bs/leo/rel_12",       &rel_switch},
      {"/mh/ds/bs/leo/rel_13",       &rel_switch},
    };


    void reset_counters(byte index, byte param)
    {
      byte i;
      if (param == 1)
      {
        for (i = 0; i < 3; i++)
        {
          Main_Data_byte[i] = 0;
          EEPROM.write(i, Main_Data_byte[i]);
        }
        Mqtt_Update_flag = 0xFF;
        client.publish(MQTT_Subsc_Data[0].Mqtt_Sub_String, "OFF", true);
      }
    }

    void rel_switch(byte index, byte param)
    {

      digitalWrite (DIG_Pins[index - 1], param);

      if( param == 0)
        Main_Data_byte[RELEY_STATE] &= ~(1 << index-1);
      else
        Main_Data_byte[RELEY_STATE] |= (1 << index-1);
      EEPROM.write(RELEY_STATE, Main_Data_byte[RELEY_STATE]);
    }

    void callback(char* topic, byte* payload, unsigned int length)
    {
      char    message[15];
      char    i;

      if(length>14)
      return;

      for (i = 0; i < length; i++)
        message[i] = (char) payload[i];
      message[i] = 0;

      for (i = 0; i < MQTT_NUM_SUB_TOP; i++)
      {
        if ( strcmp(topic, MQTT_Subsc_Data[i].Mqtt_Sub_String) == 0 )
        {
          if (strcmp(message, "ON") == 0 )
            (*(MQTT_Subsc_Data[i].function))(i, 1);
          else
            (*(MQTT_Subsc_Data[i].function))(i, 0);
        }
      }
    }

    void mqtt_subscribe(void)
    {
      byte i;
      for (i = 0; i < MQTT_NUM_SUB_TOP; i++)
      {
        client.subscribe(MQTT_Subsc_Data[i].Mqtt_Sub_String);
        client.loop(); //mqtt loop
      }
    }

     
     
    Последнее редактирование: 19 дек 2018
    yden нравится это.
  3. yden

    yden Гик

    Не подскажите, как избавиться от Стринг в строке:
    client.publish("ihouse/climat/san/temp", String(t_san).c_str());

    t_san - тип int, но есть строки где boolean.

    ?

    благодарю
     
  4. SergeiL

    SergeiL Оракул Модератор

    Использовать itoa:

    Код (C++):

    char buff[15];

          itoa(t_san, buff, 10);
          client.publish("ihouse/climat/san/temp", buff);

          itoa(h_san, buff, 10);
          client.publish("ihouse/climat/san/hum", buff);