Используем совмещенные на одной плате Arduino UNO и ESP8266 WiFi для мониторинга работы центрального контроллера водяных теплых полов.
Ранее научился отправлять показания датчиков при помощи связки Arduino UNO и ESP8266 WiFi в облачные сервисы RemoteXY и Cayenne.
Сервис ThingSpeak показался более интересным именно для сбора и обработки данных.
Постановка задачи мониторинга теплого пола.
Достигнутая цель выглядит так:
Использую вот такую плату Arduino UNO с модулем ESP-1 ESP8266 WiFi:
В предыдущей статье описывал то, как добился отправки этой платой POST запроса на сервер регистрации данных.
Научим эту плату отправлять данные на сервис ThingSpeak.
Возможно имело бы смысл воспользоваться собственной библиотекой сервиса для работы с ESP8266 thingspeak-arduino - так поступил в случае с RemoteXY и Cayenne, использовав библиотеки для работы ESP8266 каждого из этих сервисов.
Но будем использовать AT-команды и попытаемся разобраться с AT-командами самостоятельно.
Тем более что есть более-менее понятные примеры:
Будет решаться конкретная практическая задача мониторинга состояния системы зонального отопления водяными теплыми полами, постановку задачи которого осуществил в статье Сервер теплого пола на MaxSite CMS.
До написания плагина в рамках MaxSite CMS руки еще не дошли, но, похоже, сервис ThingSpeak дает возможности не только собирать информацию, но и получать собранные данные в годном для автоматизированной обработки виде, а также обрабатывать собранные данные собственными средствами MATLAB.
Описание системы дистанционного мониторинга теплого пола в облачном сервисе.
Плата, совмещающая в себе два устройства: Arduino UNO и ESP8266 WiFi, накладывает ограничения на вывод диагностической информации в SerialPort, поскольку SerialPort обеих в работе должны быть объединены переключателями на плате.
Поэтому для вывода будем использовать Lcd 2004A - поле символов 20*4 позволит вывести всю диагностическую информацию.
Будем принимать сигналы о наличии 220В на открытие направления теплого пола от четырех направлений на центральном хабе-контроллере теплых полов.
Сигнал управления на головки коллектора теплого пола в виде 220В выдают комнатные терморегуляторы.
Наличие напряжения управления головками 220В будем определять при помощи вот этой платы:
Работа с платой описывалась в статье о контроле 220В при помощи Arduino, в том числе и описывались проблемы с использованием входов A4, A5.
Говоря коротко, не получилось использовать A4, A5, но это не значит что их нельзя использовать.
Наверное можно использовать и часть цифровых входов.
Но у меня 4 направления и мне хватит A0-A3.
Отправку показаний входов будем осуществлять только при изменении значений одного из входов.
Если входы не меняются в течении заданного времени - будем делать тестовую передачу не измененных значений входов и текущего RSS WiFi.
Текущие значения входов будем отображать на дисплее.
Также будем отображать состояние пяти шагов отправки данных на сервер.
Кроме того, будем контролировать уровень связи с точкой доступа.
Способ получения RSSI WiFi точки доступа, к которой подключен ESP8266 в качестве клиента, описывается в статье Получаем уровень связи WiFi модуля ESP8266.
RSSI WiFi будем отображать на дисплее и посылать на сервер вместе с измененными значениями входов.
Если начинается череда ошибок будем перезагружать ESP8266.
Скетч Arduino мониторинга теплого пола.
#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd( 0x27 , 20 , 4 ); String ssid ="Keenetic-0138"; String password="*******"; String API = "************"; String HOST = "api.thingspeak.com"; String PORT = "80"; // строки, которые пригодятся для тестирования в мониторе порта // AT+CWJAP="Keenetic-0138","*********" // AT+CIPSTART=0,"TCP","api.thingspeak.com",80 // AT+CIPSEND=0,90 // GET /update?api_key=***************&field1=1&field2=1&field3=1&field4=0&field5=0&field6=0 boolean found = false; boolean sendnook = false; int valSensor = 1; // вспомогательные переменные String data; String fields; int value; boolean current = false; // какие пины соответствуют каким по счету датчикам int analog1 = 1; int analog2 = 2; int analog3 = 3; int analog4 = 4; int analog5 = 5; // текущее состояние входов boolean last1 = false; boolean last2 = false; boolean last3 = false; boolean last4 = false; boolean last5 = false; boolean outflag = true; // признак изменений, требующих отправки - начнем с отправки значений #define PIN_SWITCH_1 12 #define PIN_SWITCH_2 13 String curRSSI=""; int crssi=0; int errorscount=0; int maxerrorscount=10; unsigned long lastConnectionTime = 0; unsigned long lastInfoTime = 0; const unsigned long postingInterval = 1200000; // 20 минут const unsigned long infoInterval = 1000; // 1 сек long day = 86400000; // 86400000 milliseconds in a day long hour = 3600000; // 3600000 milliseconds in an hour long minute = 60000; // 60000 milliseconds in a minute long second = 1000; // 1000 milliseconds in a second void setup() { lcd.init(); lcd.backlight(); lcd.setCursor(0, 1); lcd.print("S1:" + String(last1)); lcd.setCursor(6, 1); lcd.print("S2:" + String(last2)); lcd.setCursor(12, 1); lcd.print("S3:" + String(last3)); lcd.setCursor(0, 2); lcd.print("S4:" + String(last4)); lcd.setCursor(6, 2); lcd.print("220V:" + String(last4)); pinMode (PIN_SWITCH_1, OUTPUT); pinMode (PIN_SWITCH_2, OUTPUT); Serial.begin(115200); reset(); connectWifi(); } void reset() { sendC("AT+RST", 5, "OK"); delay(1000); } void connectWifi() { lcd.setCursor(0, 0); lcd.print("WiFi:GO"); sendC("AT+CWMODE=3", 100, "OK"); while(!found) sendC("AT+CWJAP=\"" + ssid + "\",\"" + password + "\"", 4000, "OK"); if (found) { digitalWrite(PIN_SWITCH_2, HIGH); lcd.setCursor(0, 0); lcd.print("WiFi:OK"); } else { digitalWrite(PIN_SWITCH_2, LOW); lcd.setCursor(0, 0); lcd.print("WiFi:NO"); } Serial.println("ATE0"); // не знаю что это, но так работает лучше } void loop () { // вероятно проверки входов можно было бы и свернуть в одну функцию delay(500); value = analogRead(analog1); if(value < 500) current = true; else current = false; if(last1 != current) { outflag = true; last1 = current; lcd.setCursor(0, 1); lcd.print("S1:" + String(last1)); } value = analogRead(analog2); if(value < 500) current = true; else current = false; if(last2 != current) { outflag = true; last2 = current; lcd.setCursor(6, 1); lcd.print("S2:" + String(last2)); } value = analogRead(analog3); if(value < 500) current = true; else current = false; if(last3 != current) { outflag = true; last3 = current; lcd.setCursor(12, 1); lcd.print("S3:" + String(last3)); } value = analogRead(analog4); if(value < 500) current = true; else current = false; if(last4 != current) { outflag = true; last4 = current; lcd.setCursor(0, 2); lcd.print("S4:" + String(last4)); } value = analogRead(analog5); if(value < 500) current = true; else current = false; if(last5 != current) { outflag = true; last5 = current; lcd.setCursor(6, 2); lcd.print("220V:" + String(last5)); } // счетчики if (millis() - lastConnectionTime > postingInterval) { outflag = true; } if (millis() - lastInfoTime > infoInterval) { lastInfoTime = millis(); time(postingInterval+lastConnectionTime-millis()); } // отправка на сервер, если надо if (outflag || sendnook) { sendnook = true; // пока ничего не отправлено httppost(); outflag = false; // обнуляем флаг изменений входов } // счетчик ошибок lcd.setCursor(7, 3); lcd.print("Err:" + String(errorscount)); if (errorscount>maxerrorscount) { reset(); connectWifi(); errorscount = 0; } delay(1000); getRSSI(); } void httppost () { int iserror = 1; // предполагаем что будет ошибка found = false; // флаг успешной отправки на шаге итерации fields = "field1=" + String(last1)+"&field2=" + String(last2)+"&field3=" + String(last3)+"&field4=" + String(last4)+"&field5=" + String(last5); if (crssi) fields = fields + "&field6=-" + curRSSI; String data = "GET /update?api_key=" + API + "&" + fields + "\r\n"; lcd.setCursor(11, 0); lcd.print("1Post "); sendC("AT+CIPMUX=1", 2000, "OK"); if (found) { lcd.setCursor(11, 0); lcd.print("2Start "); sendC("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT, 2000, "OK"); if (found) { lcd.setCursor(11, 0); lcd.print("3Connect "); sendC("AT+CIPSEND=0," + String(data.length()+2), 2000, ">"); if (found) { lcd.setCursor(11, 0); lcd.print("4Send "); Serial.println(data); sendnook = false; delay(2000); lcd.setCursor(11, 0); lcd.print("5Send OK "); iserror = 0; // ошибки не было errorscount = 0; lcd.setCursor(7, 3); lcd.print(" "); } else Serial.println("AT+CIPCLOSE"); } } sendC("AT+CIPMUX=0", 100, "OK"); if (iserror) errorscount++; // счетчик шибок else errorscount=0; // обнуляем ошибки lastConnectionTime = millis(); lcd.setCursor(0, 3); lcd.print(" "); } void getRSSI() { char c1; String val = ""; int i; curRSSI = ""; Serial.print("AT+CWLAP=\"" + ssid + "\"\r\n"); delay(500); if (Serial.available()) { while (Serial.available() > 0) { c1 = (char)Serial.read(); val = val + c1; delay(2); } int pos = val.indexOf(ssid); pos = pos+ssid.length()+3; curRSSI = val.substring(pos, pos+2); Serial.println(curRSSI); crssi = curRSSI.toInt(); if (crssi) { lcd.setCursor(5, 0); lcd.print(-crssi); } } } void sendC(String command, int maxTime, char readReplay[]) { found = false; Serial.println(command); delay(maxTime); if (Serial.find(readReplay)) { found = true; digitalWrite(PIN_SWITCH_1, HIGH); } else { digitalWrite(PIN_SWITCH_1, LOW); } } void time(long timeNow) { int days = timeNow / day ; / int hours = (timeNow % day) / hour; int minutes = ((timeNow % day) % hour) / minute ; int seconds = (((timeNow % day) % hour) % minute) / second; lcd.setCursor(0, 3); printDigits(minutes); lcd.setCursor(2, 3); lcd.print(":"); printDigits(seconds); } void printDigits(byte digits) { if(digits < 10) lcd.print('0'); lcd.print(digits,DEC); }
Оборудование для мониторинга теплого пола.
Подключим теперь платы снятия показаний и передачи на сервер к центру управления коллектором теплого пола Beok CCT-10.
Beok CCT-10 порадовал вместимостью, позволяющей принять и скоммутировать все провода.
Добавим трехцветный светодиод отображения состояния связи/передачи/ошибок.
Добавим кнопку включения/выключения подсветки дисплея и кнопку принудительной (тестовой) отправки данных.
Переключатель слева внизу будет осуществлять выбор режима работы котла: ручной/автоматический.
Если будет отсутствовать 220В, то котел будет работать от источника бесперебойного питания.
Но без 220В контакты управления котлом будут разомкнуты внутри Beok CCT-10 и их необходимо принудительно замкнуть переключателем.
Результаты мониторинга водяного теплого пола.
Скетч с кнопками и светодиодом будет такой.
#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd( 0x27 , 20 , 4 ); #define BUTTON_PIN1 3 #define BUTTON_PIN2 4 #define PIN_SWITCH_1 11 // ошибка red #define PIN_SWITCH_2 12 // связь установлена blue #define PIN_SWITCH_3 13 // передача прошла green boolean buttonWasUp1 = true; // была ли кнопка отпущена? boolean buttonWasUp2 = true; // была ли кнопка отпущена? boolean ledEnabled = false; // включен ли свет? boolean sendbutton = false; boolean sendtimer = false; String ssid ="Keenetic-0138"; String password="***********"; String API = "************"; String HOST = "api.thingspeak.com"; String PORT = "80"; // строки, которые пригодятся для тестирования в мониторе порта // AT+CWJAP="Keenetic-0138","**********" // AT+CIPSTART=0,"TCP","api.thingspeak.com",80 // AT+CIPSEND=0,90 // GET /update?api_key=***************&field1=1&field2=1&field3=1&field4=0&field5=0&field6=0 boolean found = false; boolean sendnook = false; int valSensor = 1; // вспомогательные переменные String data; String fields; int value; boolean current = false; // какие пины соответствуют каким по счету датчикам int analog1 = 1; int analog2 = 2; int analog3 = 3; int analog4 = 4; int analog5 = 5; // текущее состояние входов boolean last1 = false; boolean last2 = false; boolean last3 = false; boolean last4 = false; boolean last5 = false; boolean outflag = true; // признак изменений, требующих отправки - начнем с отправки значений String curRSSI=""; int crssi=0; int errorscount=0; int maxerrorscount=10; unsigned long lastConnectionTime = 0; unsigned long lastOkConnectionTime = 0; unsigned long lastInfoTime = 0; const unsigned long postingInterval = 1200000; // 20 минут const unsigned long infoInterval = 1000; // 1 сек long day = 86400000; // 86400000 milliseconds in a day long hour = 3600000; // 3600000 milliseconds in an hour long minute = 60000; // 60000 milliseconds in a minute long second = 1000; // 1000 milliseconds in a second void setup() { pinMode(BUTTON_PIN1, INPUT_PULLUP); pinMode(BUTTON_PIN2, INPUT_PULLUP); lcd.init(); lcd.setCursor(0, 1); lcd.print("S1:" + String(last1)); lcd.setCursor(6, 1); lcd.print("S2:" + String(last2)); lcd.setCursor(12, 1); lcd.print("S3:" + String(last3)); lcd.setCursor(0, 2); lcd.print("S4:" + String(last4)); lcd.setCursor(6, 2); lcd.print("220V:" + String(last4)); pinMode (PIN_SWITCH_1, OUTPUT); pinMode (PIN_SWITCH_2, OUTPUT); pinMode (PIN_SWITCH_3, OUTPUT); Serial.begin(115200); reset(); connectWifi(); } void reset() { sendC("AT+RST", 5, "OK"); delay(1000); } void connectWifi() { lcd.setCursor(0, 0); lcd.print("WiFi:GO"); sendC("AT+CWMODE=3", 100, "OK"); while(!found) sendC("AT+CWJAP=\"" + ssid + "\",\"" + password + "\"", 4000, "OK"); if (found) { digitalWrite(PIN_SWITCH_1, LOW); digitalWrite(PIN_SWITCH_2, HIGH); digitalWrite(PIN_SWITCH_3, LOW); lcd.setCursor(0, 0); lcd.print("WiFi:OK"); } else { digitalWrite(PIN_SWITCH_1, LOW); digitalWrite(PIN_SWITCH_2, LOW); digitalWrite(PIN_SWITCH_3, LOW); lcd.setCursor(0, 0); lcd.print("WiFi:NO"); } Serial.println("ATE0"); // не знаю что это, но так работает лучше } void loop () { boolean buttonIsUp1 = digitalRead(BUTTON_PIN1); if (buttonWasUp1 && !buttonIsUp1) { delay(10); buttonIsUp1 = digitalRead(BUTTON_PIN1); if (!buttonIsUp1) { ledEnabled = !ledEnabled; if (ledEnabled) lcd.backlight(); else lcd.noBacklight(); } } buttonWasUp1 = buttonIsUp1; boolean buttonIsUp2 = digitalRead(BUTTON_PIN2); if (buttonWasUp2 && !buttonIsUp2) { delay(10); buttonIsUp2 = digitalRead(BUTTON_PIN2); if (!buttonIsUp2) { sendbutton = true; } } buttonWasUp2 = buttonIsUp2; // вероятно проверки входов можно было бы и свернуть в одну функцию delay(500); value = analogRead(analog1); if(value < 500) current = true; else current = false; if(last1 != current) { outflag = true; last1 = current; lcd.setCursor(0, 1); lcd.print("S1:" + String(last1)); } value = analogRead(analog2); if(value < 500) current = true; else current = false; if(last2 != current) { outflag = true; last2 = current; lcd.setCursor(6, 1); lcd.print("S2:" + String(last2)); } value = analogRead(analog3); if(value < 500) current = true; else current = false; if(last3 != current) { outflag = true; last3 = current; lcd.setCursor(12, 1); lcd.print("S3:" + String(last3)); } value = analogRead(analog4); if(value < 500) current = true; else current = false; if(last4 != current) { outflag = true; last4 = current; lcd.setCursor(0, 2); lcd.print("S4:" + String(last4)); } value = analogRead(analog5); if(value < 500) current = true; else current = false; if(last5 != current) { outflag = true; last5 = current; lcd.setCursor(6, 2); lcd.print("220V:" + String(last5)); } // счетчики if (millis() - lastConnectionTime > postingInterval) { sendtimer = true; } if (millis() - lastInfoTime > infoInterval) { lastInfoTime = millis(); time(postingInterval+lastConnectionTime-millis()); } // отправка на сервер, если надо if (outflag || sendnook || sendbutton) { sendnook = true; // пока ничего не отправлено httppost(); outflag = false; // обнуляем флаг изменений входов sendbutton = false; // обнуляем флаг принудительной отправки sendtimer = false; // обнуляем флаг отправки по времени } // счетчик ошибок lcd.setCursor(7, 3); lcd.print("Err:" + String(errorscount)); if (errorscount>maxerrorscount) { reset(); connectWifi(); errorscount = 0; } delay(1000); getRSSI(); } void httppost () { int iserror = 1; // предполагаем что будет ошибка found = false; // флаг успешной отправки на шаге итерации digitalWrite(PIN_SWITCH_2, LOW); digitalWrite(PIN_SWITCH_3, LOW); fields = "field1=" + String(last1)+"&field2=" + String(last2)+"&field3=" + String(last3)+"&field4=" + String(last4)+"&field5=" + String(last5); // if (sendtimer) fields = fields + "&field0=timer"; // else if (sendbutton) fields = fields + "&field0=button"; // else fields = fields + "&field0=custom"; if (crssi) fields = fields + "&field6=" + String(-crssi); String data = "GET /update?api_key=" + API + "&" + fields + "\r\n"; lcd.setCursor(11, 0); lcd.print("1Post "); sendC("AT+CIPMUX=1", 2000, "OK"); if (found) { lcd.setCursor(11, 0); lcd.print("2Start "); sendC("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT, 2000, "OK"); if (found) { lcd.setCursor(11, 0); lcd.print("3Connect "); sendC("AT+CIPSEND=0," + String(data.length()+2), 2000, ">"); if (found) { lcd.setCursor(11, 0); lcd.print("4Send "); Serial.println(data); sendnook = false; delay(2000); lcd.setCursor(11, 0); lcd.print("5Send OK "); iserror = 0; // ошибки не было errorscount = 0; lcd.setCursor(7, 3); lcd.print(" "); digitalWrite(PIN_SWITCH_1, LOW); digitalWrite(PIN_SWITCH_3, HIGH); lastOkConnectionTime = millis(); } else Serial.println("AT+CIPCLOSE"); } } sendC("AT+CIPMUX=0", 100, "OK"); if (iserror) { errorscount++; // счетчик шибок digitalWrite(PIN_SWITCH_1, HIGH); } else errorscount=0; // обнуляем ошибки lastConnectionTime = millis(); lcd.setCursor(0, 3); lcd.print(" "); } void getRSSI() { char c1; String val = ""; int i; curRSSI = ""; Serial.print("AT+CWLAP=\"" + ssid + "\"\r\n"); delay(500); if (Serial.available()) { while (Serial.available() > 0) { c1 = (char)Serial.read(); val = val + c1; delay(2); } int pos = val.indexOf(ssid); pos = pos+ssid.length()+3; curRSSI = val.substring(pos, pos+2); Serial.println(curRSSI); crssi = curRSSI.toInt(); if (crssi) { lcd.setCursor(5, 0); lcd.print(-crssi); } } } void sendC(String command, int maxTime, char readReplay[]) { found = false; Serial.println(command); delay(maxTime); if (Serial.find(readReplay)) { found = true; // digitalWrite(PIN_SWITCH_1, HIGH); } else { // digitalWrite(PIN_SWITCH_1, LOW); } } void time(long timeNow) { int days = timeNow / day ; int hours = (timeNow % day) / hour; int minutes = ((timeNow % day) % hour) / minute ; int seconds = (((timeNow % day) % hour) % minute) / second; lcd.setCursor(0, 3); printDigits(minutes); lcd.setCursor(2, 3); lcd.print(":"); printDigits(seconds); } void printDigits(byte digits) { if(digits < 10) lcd.print('0'); lcd.print(digits,DEC); }
Достоверность отправки данных из Arduino на облачный сервер.
Как видно из скетча, мы после отправки каждой AT команды проверяем, что пришел требуемый ответ:
sendC("AT+CIPMUX=1", 2000, "OK"); if (found) { .... sendC("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT, 2000, "OK"); if (found) { .... sendC("AT+CIPSEND=0," + String(data.length()+2), 2000, ">"); if (found) { Serial.println(data); .... iserror = 0; // ошибки не было ....
После того как получили символ ожидания данных ">" отправляем данные
Serial.println(data);
Но после отправки самих данных мы автоматически считаем, что данные отправлены, хотя нужно было бы и тут дождаться получения "OK" в ответе в Serial:
busy s...
SEND OK
CLOSED
Решение проблем получения busy s...
Оказывается все дело в неправильно определяемой длине строки, ведь println() добавляет еще символы конца строки.
Поэтому функцию отправки надо переписать с применением print() вместо println(), а в AT команды, передаваемые функции отправки, добавить "\r\n".
Итоговый скетч с правками и с проверкой всех шагов и без busy s... будет такой:
#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd( 0x27 , 20 , 4 ); #define BUTTON_PIN1 3 #define BUTTON_PIN2 4 #define PIN_SWITCH_1 11 // ошибка red #define PIN_SWITCH_2 12 // связь установлена blue #define PIN_SWITCH_3 13 // передача прошла green boolean buttonWasUp1 = true; // была ли кнопка отпущена? boolean buttonWasUp2 = true; // была ли кнопка отпущена? boolean ledEnabled = false; // включен ли свет? boolean sendbutton = false; boolean sendtimer = false; boolean senderror = false; String ssid ="Keenetic-0138"; String password="***********"; String API = "*****************"; String HOST = "api.thingspeak.com"; String PORT = "80"; // строки, которые пригодятся для тестирования в мониторе порта // AT+CWJAP="Keenetic-0138","**********" // AT+CIPSTART=0,"TCP","api.thingspeak.com",80 // AT+CIPSEND=0,90 // GET /update?api_key=**************&field1=1&field2=1&field3=1&field4=0&field5=0&field6=0 boolean found = false; boolean sendnook = false; int valSensor = 1; // вспомогательные переменные String data; String fields; int value; boolean current = false; // какие пины соответствуют каким по счету датчикам int analog1 = 1; int analog2 = 2; int analog3 = 3; int analog4 = 4; int analog5 = 5; // текущее состояние входов boolean last1 = false; boolean last2 = false; boolean last3 = false; boolean last4 = false; boolean last5 = false; boolean outflag = true; // признак изменений, требующих отправки - начнем с отправки значений String curRSSI=""; int crssi=0; int errorscount=0; int maxerrorscount=10; unsigned long lastConnectionTime = 0; unsigned long lastOkConnectionTime = 0; unsigned long lastInfoTime = 0; unsigned long lastrssTime = 0; unsigned long lasterrorTime = 0; const unsigned long postingInterval = 600000; // 10 минут const unsigned long infoInterval = 1000; // 1 сек const unsigned long rssiInterval = 300000; // 5 минут const unsigned long errorInterval = 10000; // 10 сек long day = 86400000; // 86400000 milliseconds in a day long hour = 3600000; // 3600000 milliseconds in an hour long minute = 60000; // 60000 milliseconds in a minute long second = 1000; // 1000 milliseconds in a second void setup() { pinMode(BUTTON_PIN1, INPUT_PULLUP); pinMode(BUTTON_PIN2, INPUT_PULLUP); lcd.init(); lcd.setCursor(0, 1); lcd.print("S1:" + String(last1)); lcd.setCursor(6, 1); lcd.print("S2:" + String(last2)); lcd.setCursor(12, 1); lcd.print("S3:" + String(last3)); lcd.setCursor(0, 2); lcd.print("S4:" + String(last4)); lcd.setCursor(6, 2); lcd.print("220V:" + String(last4)); pinMode (PIN_SWITCH_1, OUTPUT); pinMode (PIN_SWITCH_2, OUTPUT); pinMode (PIN_SWITCH_3, OUTPUT); Serial.begin(115200); reset(); connectWifi(); delay(2000); getRSSI();// первый раз не всегда работает при включении getRSSI(); } void reset() { sendC("AT+RST\r\n", 5, "OK"); delay(1000); } void connectWifi() { int i=0; lcd.setCursor(0, 0); lcd.print("WiFi:GO"); sendC("AT+CWMODE=3\r\n", 100, "OK"); while(!found) { i++; if (i>10) { reset(); i=0; } sendC("AT+CWJAP=\"" + ssid + "\",\"" + password + "\"\r\n", 4000, "OK"); } if (found) { digitalWrite(PIN_SWITCH_1, LOW); digitalWrite(PIN_SWITCH_2, HIGH); digitalWrite(PIN_SWITCH_3, LOW); lcd.setCursor(0, 0); lcd.print("WiFi:OK"); } else { digitalWrite(PIN_SWITCH_1, LOW); digitalWrite(PIN_SWITCH_2, LOW); digitalWrite(PIN_SWITCH_3, LOW); lcd.setCursor(0, 0); lcd.print("WiFi:NO"); } Serial.println("ATE0"); // не знаю что это, но так работает лучше } void loop () { boolean buttonIsUp1 = digitalRead(BUTTON_PIN1); if (buttonWasUp1 && !buttonIsUp1) { delay(10); buttonIsUp1 = digitalRead(BUTTON_PIN1); if (!buttonIsUp1) { ledEnabled = !ledEnabled; if (ledEnabled) lcd.backlight(); else lcd.noBacklight(); } } buttonWasUp1 = buttonIsUp1; boolean buttonIsUp2 = digitalRead(BUTTON_PIN2); if (buttonWasUp2 && !buttonIsUp2) { delay(10); buttonIsUp2 = digitalRead(BUTTON_PIN2); if (!buttonIsUp2) { sendbutton = true; } } buttonWasUp2 = buttonIsUp2; // вероятно проверки входов можно было бы и свернуть в одну функцию value = analogRead(analog1); if(value < 500) current = true; else current = false; if(last1 != current) { outflag = true; last1 = current; lcd.setCursor(0, 1); lcd.print("S1:" + String(last1)); } value = analogRead(analog2); if(value < 500) current = true; else current = false; if(last2 != current) { outflag = true; last2 = current; lcd.setCursor(6, 1); lcd.print("S2:" + String(last2)); } value = analogRead(analog3); if(value < 500) current = true; else current = false; if(last3 != current) { outflag = true; last3 = current; lcd.setCursor(12, 1); lcd.print("S3:" + String(last3)); } value = analogRead(analog4); if(value < 500) current = true; else current = false; if(last4 != current) { outflag = true; last4 = current; lcd.setCursor(0, 2); lcd.print("S4:" + String(last4)); } value = analogRead(analog5); if(value < 500) current = true; else current = false; if(last5 != current) { outflag = true; last5 = current; lcd.setCursor(6, 2); lcd.print("220V:" + String(last5)); } // счетчики if (millis() - lastConnectionTime > postingInterval) { sendtimer = true; } if (millis() - lastInfoTime > infoInterval) { lastInfoTime = millis(); time(postingInterval+lastConnectionTime-millis()); } // пропуск времени если ошибка if ( sendnook and (millis() - lasterrorTime > errorInterval) ) { senderror = true; } // отправка на сервер, если надо if ( (outflag || senderror || sendbutton || sendtimer) & (millis()>10000) ) { sendnook = true; // пока ничего не отправлено httppost(); outflag = false; // обнуляем флаг изменений входов sendbutton = false; // обнуляем флаг принудительной отправки sendtimer = false; // обнуляем флаг отправки по времени senderror = false; } if ( (millis() - lastrssTime > rssiInterval)) { lastrssTime = millis(); getRSSI(); } // счетчик ошибок lcd.setCursor(7, 3); lcd.print("Err:" + String(errorscount)); if (errorscount>maxerrorscount) { reset(); connectWifi(); errorscount = 0; } } void httppost () { int iserror = 1; // предполагаем что будет ошибка found = false; // флаг успешной отправки на шаге итерации digitalWrite(PIN_SWITCH_2, LOW); digitalWrite(PIN_SWITCH_3, LOW); fields = "field1=" + String(last1)+"&field2=" + String(last2)+"&field3=" + String(last3)+"&field4=" + String(last4)+"&field5=" + String(last5); // if (sendtimer) fields = fields + "&field0=timer"; // else if (sendbutton) fields = fields + "&field0=button"; // else fields = fields + "&field0=custom"; if (crssi) fields = fields + "&field6=" + String(-crssi); String data = "GET /update?api_key=" + API + "&" + fields + "\r\n\r\n"; lcd.setCursor(11, 0); lcd.print("1Post "); sendC("AT+CIPMUX=1\r\n", 2000, "OK"); if (found) { lcd.setCursor(11, 0); lcd.print("2Start "); sendC("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT + "\r\n", 2000, "OK"); if (found) { lcd.setCursor(11, 0); lcd.print("3Connect "); sendC("AT+CIPSEND=0," + String(data.length()) + "\r\n", 2000, ">"); if (found) { lcd.setCursor(11, 0); lcd.print("4Send "); sendC(data, 3000, "OK"); if (found) { sendnook = false; lcd.setCursor(11, 0); lcd.print("5Send OK "); iserror = 0; // ошибки не было errorscount = 0; lcd.setCursor(7, 3); lcd.print(" "); digitalWrite(PIN_SWITCH_1, LOW); digitalWrite(PIN_SWITCH_3, HIGH); lastOkConnectionTime = millis(); } else Serial.println("AT+CIPCLOSE"); } else Serial.println("AT+CIPCLOSE"); } } sendC("AT+CIPMUX=0\r\n", 100, "OK"); if (iserror) { errorscount++; // счетчик шибок digitalWrite(PIN_SWITCH_1, HIGH); lasterrorTime = millis(); } else errorscount=0; // обнуляем ошибки lastConnectionTime = millis(); lcd.setCursor(0, 3); lcd.print(" "); // Serial.println("AT+CIPCLOSE"); // потому что не всегда само закрывается } void getRSSI() { char c1; String val = ""; curRSSI = ""; int i; delay(3000); Serial.print("AT+CWLAP=\"" + ssid + "\"\r\n"); delay(2000); if (Serial.available()) { while (Serial.available() > 0) { c1 = (char)Serial.read(); val = val + c1; delay(2); } int pos = val.indexOf(ssid); pos = pos+ssid.length()+3; curRSSI = val.substring(pos, pos+2); i = curRSSI.toInt(); if (i) { crssi = i; lcd.setCursor(5, 0); lcd.print(-crssi); } } delay(2000); } void sendC(String command, int maxTime, char readReplay[]) { found = false; Serial.print(command); delay(maxTime); if (Serial.find(readReplay)) { found = true; } } void time(long timeNow) { int days = timeNow / day ; int hours = (timeNow % day) / hour; int minutes = ((timeNow % day) % hour) / minute ; int seconds = (((timeNow % day) % hour) % minute) / second; lcd.setCursor(0, 3); printDigits(minutes); lcd.setCursor(2, 3); lcd.print(":"); printDigits(seconds); } void printDigits(byte digits) { if(digits < 10) lcd.print('0'); lcd.print(digits,DEC); }
Достоверность отправки тут уже 100%.
Кроме того,в последнем скетче поправлены задержки на такие, с которыми стабильно работает.
Вот только существует вероятность, что после символов "OK" в ответе будет содержаться еще и такое:
Это возможно, если, строка данных не будет понята сервером.
Вот результаты ручной отправки команд серверу для тестирования работы, в которых видна опечатка в одной из строк данных для отправки, в результате которой возвращается ошибка 400:
(Для удобства строки команд для ручной отправки храню в теле скетча - нужно только убедиться что они копируются без пробелов в конце).
Так вот, не мешало бы содержимое ответа сервера в Serial проверить на отсутствие символов "400".
Можно поступить проще и проверить чтобы размер ответа в Serial был не больше чем "SEND OK" и "0,CLOSED".
Но это не реализовано.
Страница канала с данными работы теплого пола: thingspeak.com/channels/958186
Встраиваемые Chart IFrame для отображения данных ThingSpeak на сайте.
Сравнение уровня сигнала для ESP1 и ESP12.
Показалось что уровень связи в месте установки оборудования слишком низкий и заменил совместную плату с ESP1 и UNO на отдельную плату с модулем ESP12E, которую применял в предыдущих экспериментах совместно с платой Arduino UNO.
WeMos D1 WiFi uno based ESP8266 for arduino Compatible
Уровень связи оказался еще меньше и пришлось заменить обратно.
На графике хорошо видна обратная замена на совмещенную плату.