Здравствуйте, готовлю первый проект, который намерен использовать 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 байт.
Первое, при переменных: Код (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'ов это нормально -- сигнатура функций "ответчиков" может быть избыточна. Это поверхностный взгляд на код...
Если парит 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, т.к. она как бы "используется"
По самому коду: 1. Что будет, если в функцию callback в параметре length придёт 0? В принципе, оно безопасно отработается memcpy, но я такие вещи привык разбирать ручками, паранойя 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); }
Даже не думал, что можно так оптимизировать код, спасибо за помощь, наконец-таки понял что делал этот бесполезный код в функции callback. Кстати, касаемо условия connect/disconnect в программе - правильно додумал поставить?)): Код (C++): if (client.connect("DHTClient")) { .......... } else { client.disconnect(); }
Нет, неправильно поставили, перевожу на русский: "если клиент законнектился, то выполнить это, иначе - отконнектиться.". У вас client.dicsonnect выполняется только в том случае, если коннект неуспешен, что - неверно. Переместите строчку client.disconnect перед закрывающей скобкой блока if{ }.
Не совсем уверен, что понял правильно. Вот так? Спойлер: Цикл loop Код (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 секунд } }
Вот так: Код (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.
Тут явная ошибочка, т.к. Брокер ругается на ошибку и данные не отправляются. Спойлер: Код Код (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 секунд } Так не правильнее будет: Спойлер: Код 2: Код (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, экономит память чуток)
Значит, метод publish не отсылает данные сразу, видимо. Строго говоря, я уже писал, как я бы сделал: просто проверял, законнекчен ли клиент. Если да - делал бы publish, если нет - коннектился бы. Потому как каждый раз дёргать connect/disconnect - тяжёлые операции.
Особо не знал как это правильно реализовать, за основу взял стандартный пример из 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() ? Если есть у кого ссылки с описанием методов, скиньте пожалуйста, заранее спасибо за помощь в освоении программирования))
А по коду, что скажете?) Правильно сделал? P.S. Диагностировал и отслеживал через Отладочный терминал mosquitto, disconnect'ов появляющихся самих по себе нет, вроде всё стабильно работает)
Специально учился на одном датчике как более оптимально организовать обмен данными по протоколу MQTT. Цель достигнута, благодаря Вашей помощи! Теперь нацелен сделать управление восемью реле по протоколу MQTT с обратной связью. Использую систему домашней автоматизации MajorDoMo, там возможность организации двусторонней связи почти по любому протоколу. Цель: управление по MQTT реле с возможностью считывания состояния реле и запоминания состояния реле при отключении питания на Arduino. Потом объединить в один код Управление реле и DHT22. Касаемо сохранения состояния Реле - EEPROM, а вот с чего начать код на Arduino и как, пока не знаю. У Вас есть идеи через что это организовать?))
А чего там организовывать? Есть класс EEPROM для записи/чтения из EEPROM, его и юзайте: как пришла команда на переключение реле, переключаете его и сохраняете текущий статус реле в EEPROM. При старте МК в setup - вычитываете все статусы реле из EEPROM и выставляете их, вот и всё.