Метка: esp8266

Web-интерфейс с нуля на NodeMCU ч. 2

Как повелевать html’ом, не захламляя код

На самом деле существует два варианта, как подцеплять html/css/js и отправлять его в библиотеку ESP8266WebServer. Первый намного удобнее при использовании языка программирования Lua. Нода по сути шьется кодом, который позволяет загрузку файлов прямо в флеш память.

Я решил пойти более примитивным путем — весь код от разных страниц хранится в одном заголовчном файле, например, «html.h».

Примитивненько в коде указываем новую константу, и присваиваем ей массив char-символов.

const char html_code[] PROGMEM = "";

Именно таким решением я воспользовался в первый раз, но возникла небольшая проблема — каждый перенос строки пришлось экранировать обратным слэшем, а также добавить пернос строки с помощью комибнации «\r\n». Такой код выглядел немного нелепо, а редактировать его было неприятно. Вскоре я наткнулся на более приятную фишечку, которая позволила избавиться от столь большого количества бесполезных элементов. Начиная уже с С++11 можно использовать литеру R после символа присваивания, для того, чтобы компилятор воспринимал текст буквально «сырым», то есть raw.

Единственное, о чем нужно позаботиться — указать уникальные делиметеры, которые позволят компилятору успешно скомпилировать код.

Синтаксис будет выглядить примерно следующим образом.

const char html_code_raw[] PROGMEM = R"delimeter(CHAR_ARRAY_HERE)delimeter";

delimeter может быть абсолютно пустым, если вы уверены, что в вашем массиве символов не найдется комбинации из . Иначе же заполняем его любой конструкцией до 16 символов длиной.

Также уже опыт подсказывает, что Ардуино не очень любит работать с большими объемами данных. Сразу отправляем весь массив данных в Flash-память через модификатор PROGMEM. При наличии больших объемов информации, о RAM-памяти вообще можно не вспоминать, поскольку данные хранятся в другой памяти. А вот чтобы зацепить их оттуда, нужно прибегнуть к следующему медоду:

size_html_code_raw = strlen_P(html_code_raw);
pgm_read_byte_near(html_code_raw + bias);

Пересчитывать символы каким-то другим методом будет несколько бездумно. Сразу цепляем количество символов через процедуру strlen_P(), а затем вытаскиваем посимвольно pgm_read_byte_near() — в аргументе нужно указать сумму из адреса переменной и индекса элемента.

Теперь все просто, собираем наш код так, как нам необходимо и отправляем его в долгое плавание:

server.send(200, "text/html", html);

Зачем тут WebSocket’ы и JSON???

На самом деле данная связка очень хорошо себя проявляет. Буквально полтора-два года назад, WS основательно закрепился в современных браузерах. С помощью JS и WS на клиенте достаточно нескольких десятков строк, для того чтобы обмениваться данными. На ESP8266 оказалось приятным бонусом возможность в одно мгновение написать аналогичное решение.

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

#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#define WEBSOCKETS_SERVER_CLIENT_MAX (20)
WebSocketsServer webSocket = WebSocketsServer(81);

Подключаем все нужные библиотеки и инициализируем класс вебсокетов на 81 порту. В библиотеке настроены некоторые ограничения на количество клиентов — максимум 5 устройств. Меня это не устраивает, так что на третьей строке я изменяю константу, ограничивающую этот параметр.

webSocket.begin();

В setup() выполняем метод begin()

webSocket.loop();

А в цикле loop() обновляем состояние всех сокетов. В моем случае, мне требовалось передавать массив из чисел.

DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& data = root.createNestedArray("data");
for (int i = 0; i != array_size; i++) {
    data.add(*(array + i));
}
root.printTo(*output);

Сначала создадим буффер, в котором мы временно будем хранить JSON-объект. Затем в этом буффере создадим корневой элемент, а в этом корневом элементе создадим необходимый нам массив.

С помощью цикла, заполняем массив используя метод .add(). В качестве аргумента указываем адрес на значение нашего массива.

Чтобы получить результат нашей работы в виде String элемента, нужно воспользоваться методом .printTo() для корневого элемента. После этого, JSON-объект нам уже больше не пригодится.

Отправка готового String-объекта для всех устройств сразу производится одной строчкой:

webSocket.broadcastTXT(*output);

Отправка клиента для определенного клиента выполняется следующим образом:

webSocket.sendTXT(device_id, *output);

Внутри страницы, которую вы передаете клиентам, нужно использовать следующий код:

<script>
var socket = new WebSocket("ws://192.168.1.1:81");
socket.onopen = function () {
    alert('Соединение установлено.');
};
socket.onclose = function (event) {
    if (!event.wasClean) {
        alert('Обрыв соединения'); 
    }
};
socket.onmessage = function (event) {
    var root = JSON.parse(event.data);
    for (i = 1; i < 200; i++) {
        // Каждый элемент теперь доступен через root.data[i]
    }
};
</script>

Теперь у клиента при подключении появится всплываюшее окно с надписью «Соединение установлено», а при разрыве таким же образом будет показано сообщение о обрыве соединения. А в тот момент, когда сервер будет выполнять отправку данных, на клиенте будет вызываться метод socket.onmessage = function (event) {}, в котором можно обрабатывать входные данные.

Это конкретные эпизоды, которые я реализовал на своем ESP8266. Здесь соответственно нет приема данных на ESP8266 через WS, а также нету парсинга JSON. Возможно, что я добавлю это несколько позже, но никто не запрещает вам ознакомиться с этими библиотеками прямо сейчас.

https://github.com/Links2004/arduinoWebSockets

https://github.com/bblanchon/ArduinoJson

Web-интерфейс с нуля на NodeMCU ч. 1

Как я купил себе новую игрушку

Как-то раз я решился купить себе немного электронной атрибутики прямо с AliExpress’a. И именно тогда я столкнулся там с этим чудом: NodeMCU V3 на базе ESP8266. Маленькая Arduino Nano не просто загрустила, а вообще перестала кочевать и сейчас застряла в немного другом проекте.

Изображение NodeMCU V3 на базе ESP8266 с AliExpress
Вообще, я выделил для себя следующие преимущества данного модуля:

  • WIFI, который может одновременно быть точкой доступа и быть подключенным к другой точке
  • CH340, который позволяет через USB эмулирует все «сладости» COM-порта
  • MicroUSB, а не Mini — то есть из коробки можно подкинуть обычный «шнурок» от твоего андроида
  • 80MHZ(160MHZ от одной строчки) — 16MHZ Arduino Nano просто сидит на месте по сравнению с NodeMCU
  • 4MB Flash-памяти — приятное дополнение, хотя оно может ввести в заблуждение, так это не RAM-память, которой там всего 80 KB
  • Цена — 140-150 рублей за штуку. Немного дороже. чем голый ESP8266, но зато тут куча «вкусных плюшек»
  • Arduino IDE — возможность собирать прошивку прямо из этой среды

Из минусов пока не понравилось следующее:

  • Единственный аналоговый порт ввода/вывода, который причем еще и работает в диапазоне 3.3В

Немного про IDE

Когда я начал писать проект под эту плату, я делал это при помощи Arduino IDE. Но эта недоIDE избивает любого: того, кто пытается писать больше 100 строк, того, кто пробует написать что-то нормальное. Достаточно дернуть файл через Notepad++, подправить в нем строки, а затем вернутся обратно, чтобы увидеть прыгающие строки с разными отступами. Про какую-то гибкость, удобство и логику тут вообще нет смысла говорить, потому что в среде отсутствуют многие элементарные настройки.

Вторая попытка была на Visual Studio 2015 с плагином vMicro, позволяющим быстро создать проект под платформу Arduino и ей подобные. Все было неплохо, пока плагин не отвалился после конца Trial-режима.

Слава богу на горизонте появился некий хабровчанин, который призывал всех опомниться и попробовать некую VisualCode Studio. PlatformIO — отличный плагин для этой среды, и он отлично сейчас себя зарекомендовал, без проблем собирая мой проект.

Особенной похвалы заслуживает маленькая панель снизу, которая отвечает за все основное взаимодействие с проектом.

Собрать проект, залить на плату, открыть терминал для подключения к плате — это вызывается либо с хот-кеев, либо отсюда.

Поднимаем Web-сервер на этой малютке

Поднимается сервер буквально в четыре строки:

ESP8266WebServer server(80);
void setup {
    ...
    server.on("/page", method);
    server.begin();
    ...
}
void loop {
    ...
    server.handleClient();
    ...
}

Перечисляем адреса страниц соответственно необходимым методам, «поднимаем» сервер, а затем уже в loop-методе циклично обрабатываем все события с помощью handleClient().

Перед этим конечно пришлось немного поднастроить WiFi в NodeMCU:

WiFi.setAutoConnect(false);
IPAddress apIP(192, 168, 1, 1);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
WiFi.softAP(ssid);
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);

Первая строка здесь имеет очень важное значение. Первые мои версии проекта работали так, что NodeMCU подключалась к моему домашнему WiFi. Потом я конечно убрал эти параметры и начал создавать точку доступа. И тут я заметил небольшую странность: дома мой код работал без единой осечки, а в институте просто творилось нечто, каждые 5-10 секунд все зависало напрочь.

Разгадку я обнаружил через несколько дней — оказывается, что все это время NodeMCU пыталась подключаться к моему домашнему WiFi. А соответственно, если такой точки не было, то плата начинала жутко тупить. Даже не смотря на то, что у меня больше не было ни строчки, намекающей на подключение к WiFi.

Короче — эта строка отвечает за то, чтобы плата не пыталась автоматически подключаться к последней использованной точке.

Все остальное тоже не несет каких-то трудностей: указываем ip точки, gateway и маску, название точки — дальше запускаем нашу точку WiFi.softAPIP(). И на всякий случай, проверяем через ip через Serial.

В следующей части я рассказываю, как передавать данные в реальном времени на несколько клиентов сразу