Проверка на корректность кода...

Тема в разделе "Arduino & Shields", создана пользователем Otto, 17 июл 2016.

  1. Otto

    Otto Нерд

    Здравствуйте, готовлю первый проект, который намерен использовать 24 часа в сутки, и важна стабильность кода. Кто хорошо разбирается в программировании, просто посмотрите код и скажите, всё ли корректно я сделал для стабильной работы? Буду очень благодарен.

    Вкратце, что делает программа - Через Ethernet Shild W5100 по MQTT передаёт данные на Брокер с датчика DHT22 и выводит их в порт.
    Код (C++):
    /*
      Скетч для вывода в Монитор порта температуры в Цельсиях и Влажности
      с датчика DHT22 и отправки их по MQTT в брокер и MajorDoMo.
    */


    #include <SPI.h>                 // Библиотека SPI шины
    #include <Ethernet.h>            // Ethernet библиотека
    #include <PubSubClient.h>        // Библиотека MQTT
    #include <DHT.h>                 // Библиотека для датчиков DHT11/22

    #define DHTPIN 2                 // Номер пина, к которому подсоединен датчик
    #define DHTTYPE DHT22            // Задаём тип DHT датчика
    DHT dht(DHTPIN, DHTTYPE);


    // Задаём mac и ip адреса в Локальной сети
    byte mac[]    = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
    IPAddress ip{192, 168, 1, 74};      //ip Адрес Ethernet Shild'a Arduino
    IPAddress server{192, 168, 1, 70};  //ip Адрес для MQTT Брокера

    // Шапка Функции Callback (обратный вызов)
    void callback(char* topic, byte* payload, unsigned int length);

    EthernetClient ethClient;                                 //Инициализируем Ethernet клиент
    PubSubClient client(server, 1883, callback, ethClient);   //Инициализируем MQTT клиент


    // Функция Callback
    void callback(char* topic, byte* payload, unsigned int length)
    {
      // Выделяем необходимое кол-во памяти для копии payload
      byte* p = (byte*)malloc(length);
      // Копирование payload в новый буфер
      memcpy(p, payload, length);
      client.publish("home/data/status/sensor", p, length);
      // Освобождаем память
      free(p);
    }


    void setup()
    {
      // 1 бод равно 0.8 бит/сек
      // 1 бит/сек равно 1.25 бод
      Serial.begin(9600);             // Задаём скорость порта в БОД'ах.
      Serial.println("DHT22 test!");  // Тестовое сообщ. при откр. Монитора порта

      dht.begin();

      Ethernet.begin(mac, ip);        // Инициализируем mac, ip


    }


    void loop() {

      int h = dht.readHumidity();     // Переменная типа int для Влажности
      int t = dht.readTemperature();  // Переменная типа int для Температуры

      // Преобразуем переменные для отправки в MQTT в Брокер
      static char char_temp[7];      // Переменная для перевода из int в char
      dtostrf(t, 3, 0, char_temp);    // Перевод из int в char

      static char char_hum[7];
      dtostrf(h, 3, 0, char_hum);

      if (client.connect("DHTClient"))  //Если DHTClient подкл., то выполняется код...
      {
        if (isnan(t) || isnan(h))     // Проверка удачно ли прошло считывание с DHT22
        {
          Serial.println("Failed to read from DHT22");  // Не удалось прочитать DHT22
        } else {
          // Отправка данных в Монитор порта при удачной проверке
          Serial.print("Humidity: ");
          Serial.print(h);
          Serial.print(" %\t");
          Serial.print("Temperature: ");
          Serial.print(t);
          Serial.println(" *C");

          //Отправка данных по MQTT в Брокер
          client.publish("home/data/status/sensor/temp", char_temp);  //Температура
          client.publish("home/data/status/sensor/hum", char_hum);    //Влажность
          delay(5000);              // Отправка данных в Брокер раз в 5 секунд
        }

      } else {
        client.disconnect();      // иначе отключиться
      }
    }

    И ещё..., компилятор выдаёт такое сообщение, по смыслу вроде нечего серьёзного, правильно ли я понимаю?)):
    Код (C++):
    C:\Users\Linux\Documents\Arduino\MQTT_DHT22\MQTT_DHT22.ino:29:6: warning: unused parameter 'topic' [-Wunused-parameter]

    void callback(char* topic, byte* payload, unsigned int length)

          ^


    Скетч использует 18 474 байт (57%) памяти устройства. Всего доступно 32 256 байт.
    Глобальные переменные используют 778 байт (37%) динамической памяти, оставляя 1 270 байт для локальных переменных. Максимум: 2 048 байт.
     
     
  2. AlexU

    AlexU Гуру

    Первое, при переменных:
    Код (C++):
      int h = dht.readHumidity();     // Переменная типа int для Влажности
      int t = dht.readTemperature();  // Переменная типа int для Температуры
    условие бессмыслено:
    Код (C++):
    if (isnan(t) || isnan(h))     // Проверка удачно ли прошло считывание с DHT22
    Целое число не может быть NaN'ом. Да и проверку (при типе float, если, конечно, датчик может вернуть NaN) нужно делать сразу после получения данных, потому что все остальные операции теряют смысл.
    Второе -- зачем выделять память, копировать данные и освобождать память в функции, можно воспользоваться "оригиналом" payload:
    Код (C++):
    // Функция Callback
    void callback(char* topic, byte* payload, unsigned int length)
    {
      // Выделяем необходимое кол-во памяти для копии payload
      byte* p = (byte*)malloc(length);
      // Копирование payload в новый буфер
      memcpy(p, payload, length);
      client.publish("home/data/status/sensor", p, length);
      // Освобождаем память
      free(p);
    }
    Для callback'ов это нормально -- сигнатура функций "ответчиков" может быть избыточна.

    Это поверхностный взгляд на код...
     
    Otto нравится это.
  3. DIYMan

    DIYMan Гуру

    Если парит warning про неиспользованный параметр - пользуйте вот это:
    Код (C++):
    #define UNUSED(expr) do { (void)(expr); } while (0)
    Внутри callback пишете:

    Код (C++):
    void callback(char* topic, byte* payload, unsigned int length)
    {
       UNUSED(topic);
      ...
    }
    и всё - нет варнинга, код при этом не раздувается ни на байт, т.к. оптимизатор благополучно вырежет бессмысленную конструкцию, а анализатор не будет ругаться на unused variable, т.к. она как бы "используется" ;)
     
    Otto нравится это.
  4. DIYMan

    DIYMan Гуру

    По самому коду:

    1. Что будет, если в функцию callback в параметре length придёт 0? В принципе, оно безопасно отработается memcpy, но я такие вещи привык разбирать ручками, паранойя :eek:

    2. Все строки типа
    Код (C++):
    Serial.println("DHT22 test!");
    обернуть в макрос F - сэкономите немало оперативы. Надо, чтобы каждая строка была обёрнута в этот макрос, вот так:

    Код (C++):
    Serial.println(F("DHT22 test!"));
    3. Не увидел большого смысла в работе с памятью внутри callback - имхо можно всё напрямую передавать:
    Код (C++):
    void callback(char* topic, byte* payload, unsigned int length)
    {
      client.publish("home/data/status/sensor", payload, length);
    }
     
    Otto нравится это.
  5. Otto

    Otto Нерд

    Даже не думал, что можно так оптимизировать код, спасибо за помощь, наконец-таки понял что делал этот бесполезный код в функции callback. Кстати, касаемо условия connect/disconnect в программе - правильно додумал поставить?)):
    Код (C++):
    if (client.connect("DHTClient"))
    {
    ..........
    } else {
    client.disconnect();
    }
     
  6. DIYMan

    DIYMan Гуру

    Нет, неправильно поставили, перевожу на русский: "если клиент законнектился, то выполнить это, иначе - отконнектиться.". У вас client.dicsonnect выполняется только в том случае, если коннект неуспешен, что - неверно. Переместите строчку client.disconnect перед закрывающей скобкой блока if{ }.
     
  7. Otto

    Otto Нерд

    Не совсем уверен, что понял правильно. Вот так?
    Код (C++):
    void loop() {

      int h = dht.readHumidity();      // Переменная типа int для Влажности
      int t = dht.readTemperature();   // Переменная типа int для Температуры

      // Преобразуем переменные для отправки по MQTT в Брокер.
      // В обоих случаях используем с запасом массив символов из 7 элементов (char_temp[7]).
      static char char_temp[7];        // Переменная для перевода из int в char
      dtostrf(t, 3, 0, char_temp);     // Перевод из int в char

      static char char_hum[7];
      dtostrf(h, 3, 0, char_hum);

      client.disconnect();
      if (client.connect("DHTClient"))  // Если DHTClient подкл., то выполняется код...
      {
        // Отправка данных в Монитор порта.
        Serial.print(F("Humidity: "));  // Обёртываем в макрос F() для экономии ОЗУ
        Serial.print(h);
        Serial.print(F(" %\t"));
        Serial.print(F("Temperature: "));
        Serial.print(t);
        Serial.println(F(" *C"));

        //Отправка данных по MQTT в Брокер
        client.publish("home/data/status/sensor/temp", char_temp);  //Температура
        client.publish("home/data/status/sensor/hum", char_hum);    //Влажность
        delay(5000);              // Отправка данных в Брокер раз в 5 секунд
      }
    }
     
  8. DIYMan

    DIYMan Гуру

    Вот так:
    Код (C++):
    void loop() {

      int h = dht.readHumidity();      // Переменная типа int для Влажности
      int t = dht.readTemperature();   // Переменная типа int для Температуры

      // Преобразуем переменные для отправки по MQTT в Брокер.
      // В обоих случаях используем с запасом массив символов из 7 элементов (char_temp[7]).
      static char char_temp[7];        // Переменная для перевода из int в char
      dtostrf(t, 3, 0, char_temp);     // Перевод из int в char

      static char char_hum[7];
      dtostrf(h, 3, 0, char_hum);

      if (client.connect("DHTClient"))  // Если DHTClient подкл., то выполняется код...
      {
        // Отправка данных в Монитор порта.
        Serial.print(F("Humidity: "));  // Обёртываем в макрос F() для экономии ОЗУ
        Serial.print(h);
        Serial.print(F(" %\t"));
        Serial.print(F("Temperature: "));
        Serial.print(t);
        Serial.println(F(" *C"));

        //Отправка данных по MQTT в Брокер
        client.publish("home/data/status/sensor/temp", char_temp);  //Температура
        client.publish("home/data/status/sensor/hum", char_hum);    //Влажность
       client.disconnect();
        delay(5000);              // Отправка данных в Брокер раз в 5 секунд
      }
    }
    Ещё: dtostrf - очень тяжёлая функция, хотя, если вас устраивает - пущай будет. Но я бы поюзал itoa - всё равно у вас тип переменных h и t - int.
     
  9. Otto

    Otto Нерд


    Тут явная ошибочка, т.к. Брокер ругается на ошибку и данные не отправляются.

    Код (C++):
     if (client.connect("DHTClient"))  // Если DHTClient подкл., то выполняется код...
      {
        // Отправка данных в Монитор порта.
        Serial.print(F("Humidity: "));  // Обёртываем в макрос F() для экономии ОЗУ
        Serial.print(h);
        Serial.print(F(" %\t"));
        Serial.print(F("Temperature: "));
        Serial.print(t);
        Serial.println(F(" *C"));

        //Отправка данных по MQTT в Брокер
        client.publish("home/data/status/sensor/temp", char_temp);  //Температура
        client.publish("home/data/status/sensor/hum", char_hum);    //Влажность
        client.disconnect(); // ОШИБКА!
        delay(5000);              // Отправка данных в Брокер раз в 5 секунд
      }
    Так не правильнее будет:
    Код (C++):
      if (client.connect("DHTClient"))  // Если DHTClient подкл., то выполняется код...
      {
        // Отправка данных в Монитор порта.
        Serial.print(F("Humidity: "));  // Обёртываем в макрос F() для экономии ОЗУ
        Serial.print(h);
        Serial.print(F(" %\t"));
        Serial.print(F("Temperature: "));
        Serial.print(t);
        Serial.println(F(" *C"));

        //Отправка данных по MQTT в Брокер
        client.publish("home/data/status/sensor/temp", char_temp);  //Температура
        client.publish("home/data/status/sensor/hum", char_hum);    //Влажность
        delay(5000);              // Отправка данных в Брокер раз в 5 секунд
      } else {
        КАКАЯ НИБУДЬ ФУНКЦИЯ ЕСТЬ, НА ПОДОБИИ client.REconnect();
        ДЛЯ ПЕРЕПОДКЛЮЧЕНИЯ? ИСКАЛ В ИНЕТЕ, НЕ НАШЁЛ.
        СМЫСЛ БЫ БЫЛ: ЕСЛИ КЛИЕНТ ПОДКЛЮЧИЛСЯ - ОТПРАВЛЯЕМ ДАННЫЕ,
        ИНАЧЕ ПОПЫТКА ПЕРЕПОДКЛЮЧИТСЯ!
      }

    Спасибо, что подсказали, что существует такая функция преобразования для int как itoa, экономит память чуток)
     
  10. DIYMan

    DIYMan Гуру

    Значит, метод publish не отсылает данные сразу, видимо. Строго говоря, я уже писал, как я бы сделал: просто проверял, законнекчен ли клиент. Если да - делал бы publish, если нет - коннектился бы. Потому как каждый раз дёргать connect/disconnect - тяжёлые операции.
     
  11. Otto

    Otto Нерд

    Особо не знал как это правильно реализовать, за основу взял стандартный пример из Arduino IDE. Сделал переподключение через отдельную функцию, надеюсь, что правильно додумался всё сделать, что бы не дёргать connect / disconnect, вот весь код:
    Код (C++):
    /*
      Скетч для вывода в Монитор порта теспературы в Цельсиях и Влажности
      с датчика DHT22 и отправки их по MQTT в брокер и MajorDoMo.
    */

    #include <SPI.h>                 // Библиотека SPI шины
    #include <Ethernet.h>            // Ethernet библиотека
    #include <PubSubClient.h>        // Библиотека MQTT
    #include <DHT.h>                 // Библиотека для датчиков DHT11/22

    #define DHTPIN 2                 // Номер пина, к которому подсоединен датчик
    #define DHTTYPE DHT22            // Задаём тип DHT датчика
    DHT dht(DHTPIN, DHTTYPE);

    #define UNUSED(expr) do { (void)(expr); } while (0) /*Безсмысленный цикл для callback
                                                          убирающий предупреждение
                                                          с компилятора*/

    // Задаём mac и ip адреса в Локальной сети
    byte mac[]    = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
    IPAddress ip{192, 168, 1, 74};      //ip Адрес Ethernet Shild'a Arduino
    IPAddress server{192, 168, 1, 70};  //ip Адрес для MQTT Брокера

    // Шапка Функции Callback для объявления и Инициализации PubSubClient
    void callback(char* topic, byte* payload, unsigned int length);

    EthernetClient ethClient;                                 // Инициализируем Ethernet клиент
    PubSubClient client(server, 1883, callback, ethClient);   // Инициализируем MQTT клиент


    // Функция Callback
    void callback(char* topic, byte* payload, unsigned int length)
    {
      //Используем идентификатор UNUSED для устранения Warning'а при компиляции
      UNUSED(topic);
      client.publish("home/data/status/sensor", payload, length);
    }


    void setup()
    {
      // 1 бод равно 0.8 бит/сек
      // 1 бит/сек равно 1.25 бод
      Serial.begin(9600);                  // Задаём скорость порта в БОД'ах.
      Serial.println(F("DHT22 test!"));    // Тестовое сообщ. при откр. Монитора порта

      dht.begin();                    // Инициализируем dht
      Ethernet.begin(mac, ip);        // Инициализируем mac, ip
    }

    void reconnect() {
      // Повторяем, пока не переподключимся
      while (!client.connected()) {         // Логическое НЕ "!" - Если клиент не подкл....
        // Попытка подключиться
        if (client.connect("DHTClient")) {  // Если DHTClient клиент подключен...
          Serial.println(F("connected!"));     // Выводим сообщ., что подключено!
        } else {
          Serial.print(F("failed connected - ")); // Ощибка соединения
          Serial.println(F("Jdem 5 seconds"));    // Ждём 5 секунд
          delay(5000);
        }
      }
    }


    void loop() {

      if (!client.connect("DHTClient")) {   //Если DHTClient клиент не подключен...
        reconnect();                        //Запускаем функцию перезепуска
      }
      client.loop();


      int h = dht.readHumidity();      // Переменная типа int для Влажности
      int t = dht.readTemperature();   // Переменная типа int для Температуры

      // Преобразуем переменные для отправки по MQTT в Брокер.
      // В обоих случаях используем с запасом массив символов из 7 элементов (char_temp[7]).
      static char char_temp[7];        // Переменная для перевода из int в char
      itoa(t, char_temp, 10);          // Перевод из int в char

      static char char_hum[7];
      itoa(h, char_hum, 10);

      // Отправка данных в Монитор порта.
      Serial.print(F("Humidity: "));        // Обёртываем в макрос F() для экономии ОЗУ
      Serial.print(h);
      Serial.print(F(" %\t"));
      Serial.print(F("Temperature: "));
      Serial.print(t);
      Serial.println(F(" *C"));

      //Отправка данных по MQTT в Брокер
      client.publish("home/data/status/sensor/temp", char_temp);  //Температура
      client.publish("home/data/status/sensor/hum", char_hum);    //Влажность
      delay(2000);              // Отправка данных в Брокер раз в 2 секунды
    }

    И кстати, что делает - client.loop(); Информацию не нашёл. Так же хотелось бы знать, чем отличается client.connected() от client.connect() ?
    Если есть у кого ссылки с описанием методов, скиньте пожалуйста, заранее спасибо за помощь в освоении программирования))
     
  12. DIYMan

    DIYMan Гуру

    Первая - проверяет, законнекчен ли клиент. Вторая - коннектится.
     
  13. Otto

    Otto Нерд

    А по коду, что скажете?) Правильно сделал?
    P.S. Диагностировал и отслеживал через Отладочный терминал mosquitto, disconnect'ов появляющихся самих по себе нет, вроде всё стабильно работает)
     
  14. DIYMan

    DIYMan Гуру

    Да пойдёт код, пойдёт. Если память нигде не жмёт - можно и не париться с оптимизацией дальше ;)
     
  15. Otto

    Otto Нерд

    Специально учился на одном датчике как более оптимально организовать обмен данными по протоколу MQTT. Цель достигнута, благодаря Вашей помощи! Теперь нацелен сделать управление восемью реле по протоколу MQTT с обратной связью. Использую систему домашней автоматизации MajorDoMo, там возможность организации двусторонней связи почти по любому протоколу. Цель: управление по MQTT реле с возможностью считывания состояния реле и запоминания состояния реле при отключении питания на Arduino. Потом объединить в один код Управление реле и DHT22. Касаемо сохранения состояния Реле - EEPROM, а вот с чего начать код на Arduino и как, пока не знаю. У Вас есть идеи через что это организовать?))
     
  16. DIYMan

    DIYMan Гуру

    А чего там организовывать? Есть класс EEPROM для записи/чтения из EEPROM, его и юзайте: как пришла команда на переключение реле, переключаете его и сохраняете текущий статус реле в EEPROM. При старте МК в setup - вычитываете все статусы реле из EEPROM и выставляете их, вот и всё.