Относительно легко получилось сделать из платы ESP8266 WiFi WEB-сервер.
Попробуем теперь сделать WEB-клиента, периодически отправляющего данные о состоянии чего-нибудь на сервер. В качестве сервера будем использовать MaxSiteCMS.
Формат данных для отправки на сервер.
Для начала определимся - что и как будем отправлять на сервер.
Данные в запросе POST будут представлены в таком виде:
secret=0000&name=ESP_8266WiFi&temp=23.00&hum=63.00&gas=478&mac=F485452DE6B4&ram=960&rssi=-79
Обязательным будет наличие полей: secret и name - ключ доступа и имя платы.
Необязательные поля: ram, mac, date, rssi.
Остальные поля будут восприниматься как показания сенсоров 'sensor'=>'value'.
В полученном с сервера результате будет JSON массив с кодом ошибки или количеством добавленных сигналов.
Обработка AJAX в CMS MaxSite.
Для того чтобы POST-запрос от платы ESP8266 WiFi попал на обработку в плагин my_esp8266 стоит обратить внимание на то, как обрабатывается AJAX в CMS MaxSite.
Обработка AJAX запроса происходит в файле \application\views\ajax.php, в котором проверяются несколько условий безопасности, которым должно соответствовать обращение к MaxSite CMS. Если изучить этот файл, то можно увидеть проверки:
1. if (!isset($_SERVER['HTTP_X_REQUESTED_WITH'])).
2. mso_checkreferer()- функция, которая содержит проверки: if (!isset($_SERVER['HTTP_REFERER']))
и if ($p != $_SERVER['HTTP_HOST']).
3. $fn = $MSO->config['base_dir'] . base64_decode($MSO->data['uri_segment'][2]) - имя файла, принимающего POST и путь к нему должно быть закодировано соответствующим образом.
4. if (strpos($fn, '-ajax.php') !== false ) - имя файла должно заканчиваться на '-ajax.php'.
Все это нужно учесть и видно, что необходимый заголовок HTTP-запроса получается немаленький.
Плагин для MaxSite CMS.
Сервер должен уметь принять данные и что-то с ними сделать.
Напишем плагин my_esp8266 для MaxSiteCMS, который принимает данные. Воспользуемся встроенной в CMS возможностью принимать AJAX-запросы.
Плагин будет принимать данные и заносить их в базу данных.
Напрашиваются три таблицы:
esp_shields - таблица устройств (плат);
esp_sensors - таблица сенсоров (датчиков);
esp_signals - таблица сигналов (значений сенсора платы в момент времени).
Если пришел сигнал от неизвестного устройства - будет добавлено устройство в таблицу устройств с идентификатором mac или name. Если POST содержит значения неизвестного сенсора - будет добавлен сенсор с идентефикатором по устройству и имени сенсора.
На странице "Логи" админ-панели плагина выведем отладочные инструменты:
1. Строка из которой можно копировать кодированную ссылку на файл-обработчик и вставлять в запрос.
2. Кнопка для отправки тестового запроса для проверки работы.
3. Информация с последними параметрами POST-запроса, который попал в файл-обработчик.
Элементарные настройки функционирования:
Подробнее о плагине: Плагин my_esp8266 для MaxSiteCMS.
Отправка POST-запроса с ESP8266WiFi на сервер.
Для этого эксперимента соединим плату Arduino UNO R3 и ESP8266 ESP-12 ESP-12E UART Wi-Fi, как делали в эксперименте с WEB-сервером.
Воспользуемся библиотекой Arduino WiFi library for ESP8266 modules.
Имеется пример, в котором происходит запрос к серверу.
Вероятно, мы должны составить что-то вроде этого (заменив esp8266.mysite.ru на свой домен):
jsonString = "secret=0000&name=ESP_8266WiFi&temp=" + String(temp) + "&hum=" + String(hum) + "&gas=" + tring(gas) + "&mac=" + String(mac)+ "&ram=" + String(freeRam()) + "&rssi=" + String(WiFi.RSSI()); client.println(F("POST /ajax/cGx1Z2lucy9teV********************WpheC5waHA= HTTP/1.1")); client.println(F("Host: esp8266.mysite.ru:80")); client.println(F("User-Agent: ESP8266WiFi")); client.println(F("Accept: application/xml")); client.println(F("Content-Type: application/x-www-form-urlencoded")); client.println(F("X-Requested-With: XMLHttpRequest")); client.println(F("Referer: http://esp8266.mysite.ru")); client.println(F("Connection: close")); client.println("Content-Length: " + String(jsonString.length())); client.println(); client.println(jsonString); client.println(); client.println();
Так и не удалось добиться, чтобы скетч, содержащий этот код, работал.
Для того чтобы разобраться что к чему соединим платы так, чтобы видеть что происходит, выводя информацию при помощи Serial.println() в консоль Arduino IDE.
Эксперименты показали, что передать данные удается только когда весь заголовок передается за один вызов функции client.println().
Впервые заработал такой скетч.
#include "WiFiEsp.h" // Библиотека для работы с платой #include <dht11.h> // Добавляем библиотеку DHT11 dht11 DHT; // Объявление переменной класса dht11 #define DHT11_PIN 4 // Датчик DHT11 подключен к цифровому пину номер 4 // эмулируем серийный порт на pin 6/7 чтобы видеть обмен данными #ifndef HAVE_HWSERIAL1 #include "SoftwareSerial.h" SoftwareSerial Serial1(6, 7); // RX, TX #endif const char ssid[] = "Keenetic-0138"; // SSID const char pass[] = "********"; // пароль int status = WL_IDLE_STATUS; // статус Wifi-радиомодуля String jsonString; // данные String PostHeader; // заголовок const char server[] = "esp8266.mysite.ru"; unsigned long lastConnectionTime = 0; unsigned long lastInfoTime = 0; const unsigned long postingInterval = 1200000; // 20 минут const unsigned long infoInterval = 60000; // 1 минута // Инициализация объекта wi-fi клиент WiFiEspClient client; void setup() { Serial.begin(9600); Serial1.begin(9600); WiFi.init(&Serial1); if (WiFi.status() == WL_NO_SHIELD) { Serial.println("No WiFi shield"); while (true); } while ( status != WL_CONNECTED) { Serial.print("Connectid to: "); Serial.println(ssid); status = WiFi.begin(ssid, pass); } httpRequest(); } void loop() { if (millis() - lastConnectionTime > postingInterval) { httpRequest(); } if (millis() - lastInfoTime > infoInterval) { lastInfoTime = millis(); } } void httpRequest() { Serial.println(); client.stop(); if (client.connect(server, 80)) { Serial.println("Connection to server..."); jsonString = "secret=0000&name=ESP_8266WiFi&temp=28&hum=45&gas=32"; Serial.println(String(jsonString.length())); client.println(F("POST /ajax/cGx1Z2lucy9t**********************pheC5waHA= HTTP/1.1\r\nHost: esp8266.mysite.ru:80\r\nX-Requested-With: XMLHttpRequest\r\nReferer: http://esp8266.mysite.ru\r\nConnection: close\r\nContent-Length: 51\r\n\r\nsecret=0000&name=ESP_8266WiFi&temp=28&hum=45&gas=32\r\n\r\n\r\n")); } while (client.available()) { char c = client.read(); Serial.write(c); } if (!client.connected()) { Serial.println(); Serial.println("Disconnecting."); client.stop(); } lastConnectionTime = millis(); }
Размер передаваемых данных мы измерили вручную и сформировали константу. Такой скетч работает без проблем. Получаем ответ от сервера в виде JSON-массива, который формирует плагин. С ответом можно тоже что-то сделать - например зажечь светодиод, если нет ошибки.
Но нам же нужно передавать динамически изменяемые данные, поэтому мы не можем заранее знать результат работы функции jsonString.length().
Оказывается, что данные POST запроса можно передавать отдельным от передачи заголовка вызовом client.println().
Поэтому, мы сперва формируем переменную POST параметров, а затем формируем заголовок, включая эту длину.
Этот скетч работает уже с живыми данными.
#include "WiFiEsp.h" // Библиотека для работы с платой #include <dht11.h> // Добавляем библиотеку DHT11 dht11 DHT; // Объявление переменной класса dht11 #define DHT11_PIN 4 // Датчик DHT11 подключен к цифровому пину номер 4 // эмулируем серийный порт на pin 6/7 чтобы видеть обмен данными #ifndef HAVE_HWSERIAL1 #include "SoftwareSerial.h" SoftwareSerial Serial1(6, 7); // RX, TX #endif const int analogSignal = A3; //подключение аналогового сигналоьного пина const int digitalSignal = 8; //подключение цифрового сигнального пина float hum = 0; //переменная для хранения влажности float temp = 0; //переменная для хранения температуры char mac[18] = { 0 }; int chk; //переменная для хранения ошибки DHT const char ssid[] = "Keenetic-0138"; // SSID const char pass[] = "********"; // пароль int status = WL_IDLE_STATUS; // статус Wifi-радиомодуля String jsonString; // данные String PostHeader; // заголовок const char server[] = "esp8266.mysite.ru"; unsigned long lastConnectionTime = 0; unsigned long lastInfoTime = 0; const unsigned long postingInterval = 60000; // 1 минута const unsigned long infoInterval = 60000; // 1 минута // Инициализация объекта wi-fi клиент WiFiEspClient client; void setup() { Serial.begin(9600); Serial1.begin(9600); WiFi.init(&Serial1); if (WiFi.status() == WL_NO_SHIELD) { Serial.println("No WiFi shield"); while (true); } while ( status != WL_CONNECTED) { Serial.print("Connectid to: "); Serial.println(ssid); status = WiFi.begin(ssid, pass); } uint8_t macH[6]; WiFi.macAddress(macH); sprintf(mac, "XXXXXX", macH[0], macH[1], macH[2], macH[3], macH[4], macH[5]); httpRequest(); } void loop() { if (millis() - lastConnectionTime > postingInterval) { httpRequest(); } if (millis() - lastInfoTime > infoInterval) { // Чтобы не скучно было ждать long rssi = WiFi.RSSI(); Serial.print("(RSSI): "); Serial.print(rssi); Serial.print(" FREE RAM: "); Serial.println(freeRam()); lastInfoTime = millis(); } } void httpRequest() { chk = DHT.read(DHT11_PIN); hum = DHT.humidity; temp = DHT.temperature; Serial.println(); client.stop(); if (client.connect(server, 80)) { Serial.println("Connection to server..."); jsonString = "secret=0000&name=ESP_8266WiFi&temp=" + String(temp) + "&hum=" + String(hum) + "&mac=" + String(mac)+ "&ram=" + String(freeRam()) + "&rssi=" + String(WiFi.RSSI()); PostHeader = "POST /ajax/cGx1Z2lucy9t***********************heC5waHA= HTTP/1.1\r\n"; PostHeader += "Host: esp8266.mysite.ru:80\r\n"; PostHeader += "User-Agent: ESP8266WiFi\r\n"; PostHeader += "Accept: application/xml\r\n"; PostHeader += "Content-Type: application/x-www-form-urlencoded\r\n"; PostHeader += "X-Requested-With: XMLHttpRequest\r\n"; PostHeader += "Referer: http://esp8266.mysite.ru\r\n"; PostHeader += "Accept: application/xml\r\n"; PostHeader += "Connection: close\r\n"; PostHeader += "Content-Length: " + String(jsonString.length()) + "\r\n"; jsonString += "\r\n\r\n\r\n"; Serial.println(PostHeader); Serial.println(jsonString); } else { Serial.println("Сonnection failed"); } while (client.available()) { char c = client.read(); Serial.write(c); } if (!client.connected()) { Serial.println(); Serial.println("Disconnecting."); client.stop(); } lastConnectionTime = millis(); } int freeRam () { extern int __heap_start, *__brkval; int v; return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); }
Это максимальный объем данных с которыми скетч еще работает.
Дальнейшее увеличение объема приводит к тому, что правильно передача проходит один раз, а затем происходит срыв работы.
Несмотря на то что в консоль выведен битый заголовок - на сервер отправляются правильные данные. У нас почему-то при выводе в один серийный порт попадают данные, которые крутятся в другом. Строка параметров же отправляется такая же искореженная, как и выводится в консоль.
Видно что совсем мало памяти остается.
Разбираться не будем потому что стабильной работы удается достичь, если не использовать эмуляцию серийного порта для наблюдения за работой - нам это и не нужно уже.
Снова помещаем одну плату на другую и добавляем светодиод, чтобы отображать процесс передачи запроса на сервер.
Заливаем такой скетч.
#include "WiFiEsp.h" // Библиотека для работы с платой #include <dht11.h> // Добавляем библиотеку DHT11 dht11 DHT; // Объявление переменной класса dht11 #define DHT11_PIN 4 // Датчик DHT11 подключен к цифровому пину номер 4 const int analogSignal = A3; //подключение аналогового сигналоьного пина const int digitalSignal = 8; //подключение цифрового сигнального пина boolean noGas; //переменная для хранения значения о присутствии газа int gas = 0; //переменная для хранения количества газа float hum = 0; //переменная для хранения влажности float temp = 0; //переменная для хранения температуры char mac[18] = { 0 }; // переменная для mac адреса int chk; //переменная для хранения ошибки DHT const char ssid[] = "Keenetic-0138"; // SSID const char pass[] = "********"; // пароль int status = WL_IDLE_STATUS; // статус Wifi-радиомодуля String jsonString; // данные String PostHeader; // заголовок const char server[] = "esp8266.mysite.ru"; unsigned long lastConnectionTime = 0; unsigned long lastInfoTime = 0; const unsigned long postingInterval = 1200000; // 20 минут const unsigned long infoInterval = 60000; // 1 минута // Инициализация объекта wi-fi клиент WiFiEspClient client; void setup() { pinMode(12, OUTPUT); Serial.begin(9600); WiFi.init(&Serial); if (WiFi.status() == WL_NO_SHIELD) { while (true); } while ( status != WL_CONNECTED) { status = WiFi.begin(ssid, pass); } uint8_t macH[6]; WiFi.macAddress(macH); sprintf(mac, "XXXXXX", macH[0], macH[1], macH[2], macH[3], macH[4], macH[5]); httpRequest(); } void loop() { if (millis() - lastConnectionTime > postingInterval) { httpRequest(); } if (millis() - lastInfoTime > infoInterval) { lastInfoTime = millis(); } } void httpRequest() { noGas = digitalRead(digitalSignal); //считываем значение о присутствии газа gas = analogRead(analogSignal); // и о его количестве chk = DHT.read(DHT11_PIN); hum = DHT.humidity; temp = DHT.temperature; if (client.connect(server, 80)) { digitalWrite(12, HIGH); jsonString = "secret=0000&name=ESP_8266WiFi&temp=" + String(temp) + "&hum=" + String(hum) + "&gas=" + String(gas) + "&mac=" + String(mac)+ "&ram=" + String(freeRam()) + "&rssi=" + String(WiFi.RSSI()); PostHeader = "POST /ajax/cGx1Z2lucy9t************************eC5waHA= HTTP/1.1\r\n"; PostHeader += "Host: esp8266.mysite.ru:80\r\n"; PostHeader += "User-Agent: ESP8266WiFi\r\n"; PostHeader += "Accept: application/xml\r\n"; PostHeader += "Content-Type: application/x-www-form-urlencoded\r\n"; PostHeader += "X-Requested-With: XMLHttpRequest\r\n"; PostHeader += "Referer: http://esp8266.mysite.ru\r\n"; PostHeader += "Connection: close\r\n"; PostHeader += "Content-Length: " + String(jsonString.length()) + "\r\n"; jsonString += "\r\n\r\n\r\n"; client.println(PostHeader); client.println(jsonString); } while (client.available()) { char c = client.read(); } if (!client.connected()) { client.stop(); digitalWrite(12, LOW); } lastConnectionTime = millis(); } int freeRam () { extern int __heap_start, *__brkval; int v; return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); }
Переданные значения сигналов, измеренных сенсорами можно видеть в админке плагина:
Загрузить исходники.
Скетч для отправки данных из ARDUINO esp8266wifi в MaxSite CMS