Одна из самых больших проблем в создании объектов Интернета вещей – первая конфигурация. Открытие IDE просто для изменения сетевых учетных данных или HTML неудобно и занимает много времени. Другими вариантами будет выделенное мобильное приложение, которое вы будете использовать, вероятно, только один раз, или можно использовать CLI через последовательный порт.

Но наиболее удобным для меня является редактирование файла конфигурации, размещенного на SD-карте. В этом проекте мы создадим хорошую основу для создания управляемых через Интернет устройств с веб-интерфейсом и возможностью простой настройки.
Файл конфигурации, использующий формат JSON, очень прост и выглядит так:
{
"husarnet":{
"hostname":"esp32template",
"joincode":"fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/xxxxxxxxxxxxxxxxxxxxxx"
},
"wifi":[
{
"ssid":"mywifinet123",
"pass":"qwerty"
},
{
"ssid":"officenet",
"pass":"admin1"
},
{
"ssid":"iPhone(Johny)",
"pass":"12345"
}
]
}
Здесь есть два блока. Первый – учетные данные Husarnet. Husarnet позволяет вам получать доступ к своим «вещам» через Интернет, как если бы они находились в одной сети LAN. Второй – учетные данные WiFi. Укажите одну или несколько пар SSID-пароля, поэтому вам не нужно будет менять конфигурацию, если вы перенесете свои «вещи» куда-нибудь в другую сеть.
Редактировать HTML-файлы как таблицы символов C/C ++ не очень удобно. Вот почему стоит переместить HTML-файл на SD-карту. Весь интерфейс между веб-страницей и оборудованием осуществляется через WebSockets, поэтому вам не нужно анализировать HTTP-запросы, и ваш код станет намного чище. WebSockets также намного быстрее, чем HTTP-запросы.
Также в данном случае есть несколько преимуществ подключения к Интернету через VPN. Во-первых, очень простая настройка ваших устройств (вам просто нужно указать код соединения и придумать название для вашего устройства). Просто взгляните на JSON. Joincode генерируется только один раз, и вы можете использовать его для соединения вашего компьютера и устройств ESP32. Во-вторых, легко получить доступ к вашему устройству, как это было бы в вашей локальной сети. Веб-сайт размещен на ESP32, и вы получаете доступ к этому веб-сайту напрямую через Интернет. Husarnet предоставляет только прозрачный слой подключения, работающий как в вашей локальной сети, так и через Интернет одинаково. Husarnet – это в основном VPN. И, в-третьих, низкая задержка. Ваш компьютер (с установленным клиентом Husarnet) и ESP32 подключены однорангово. Инфраструктура Husarnet помогает вашим устройствам находить друг друга только через Интернет и не используется для пересылки пользовательских данных после установления P2P-соединения. Если ваши устройства находятся в одной сети, вы будете управлять им так же, как по локальной сети.
Демонстрационный пример действительно простой.

В нем можно управлять светодиодом, подключенным к ESP32, с помощью кнопки в веб-интерфейсе. ESP32 отправляет значение счетчика каждые 100 мс. ESP32 отправляет состояние кнопки, чтобы изменить цвет точки в веб-интерфейсе.
Схема подключения для данного примера приведена далее.

Подключите кнопку между контактом G10 и GND. Подключите светодиод с последовательным резистором между контактом G11 и заземлением. Подключите модуль SD-карты к вашей плате ESP32: если у вас уже есть плата ESP32 для девайса с гнездом для SD-карты, могут быть использованы другие контакты, поэтому вам придется изменить также программную часть.
Вам необходимо отформатировать SD-карту в формате FAT16/FAT32. После этого скопируйте файлы setting.js и index.htm на SD-карту, которую вы только что отформатировали. Измените файл settings.js, указав свои собственные учетные данные сети Wi-Fi и учетные данные Husarnet. Чтобы найти свой код для подключения к Husarnet нужно зарегистрироваться на https://app.husarnet.com, создать новую сеть или выбрать существующую, в выбранной сети нажмите кнопку Add element и перейдите на вкладку «join code». Сохраните ваши файлы и подключите SD-карту к вашему ESP32.
Чтобы запустить проект, откройте Arduino IDE и выполните следующие действия. Установите пакет Husarnet для ESP32. Для этого нужно открыть Файл – Настройки (File - Preferences), в поле Дополнительные URL-адреса менеджера плат (Additional Board Manager URLs) добавьте ссылку https://files.husarion.com/arduino/beta/package_esp32_husarnet_index.json. Откройте Менеджер плат (Boards Manager), Найдите esp32-husarnet. Нажмите кнопку Установить (Install). Выберите ESP32 Dev Board. Откройте Инструменты – Плата (Tools - Board), выберите ESP32 Dev Module в разделе «ESP32 Arduino (Husarnet)».
Теперь нужно установить библиотеку ArduinoJson. Откройте Инструменты – Управление библиотеками (Tools – Manage Libraries), найдите ArduinoJson, нажмите кнопку установить. После этого следует установить библиотеку arduinoWebSockets (Husarnet fork). Загрузите https://github.com/husarnet/arduinoWebSockets в виде ZIP-файла (это совместимый с Husarnet форк arduinoWebSockets от Links2004 (Markus)). Откройте Скетч – Подключить библиотеку – Добавить ZIP библиотеку (Sketch – Include Library – Add .ZIP Library). Выберите файл arduinoWebSockets-master.zip, который вы только что скачали, и нажмите кнопку Открыть.
После этого можно приступить к программированию платы ESP32. Откройте проект ESP32-web-template-sd.ino, загрузить проект в свою плату ESP32. Откройте WebUI: есть два варианта:
- войдите в свою учетную запись на https://app.husarnet.com, найдите устройство esp32template, которое вы только что подключили, и нажмите веб-кнопку UIbutton. Вы также можете щелкнуть элемент esp32template, чтобы открыть «Element settings» и выбрать «Make the Web UI public», если вы хотите иметь общедоступный адрес. В таком сценарии прокси-серверы Husarnet используются для предоставления вам веб-интерфейса.
- вариант P2P – добавьте ваш ноутбук в ту же сеть Husarnet, что и плата ESP32. В этом сценарии прокси-серверы не используются, и вы подключаетесь к ESP32 с очень низкой задержкой напрямую, без переадресации портов на вашем маршрутизаторе. В настоящее время доступен только Linux-клиент, поэтому откройте свой Linux-терминал и введите для установки Husarnet «$ curl https://install.husarnet.com/install.sh | sudo bash». В «$ husarnet join XXXXXXXXXXXXXXXXXXXXXXX» замените XXX вашим собственным кодом соединения.
Чтобы найти свой код соединения, нужно зарегистрироваться или войти на https://app.husarnet.com, создать новую сеть или выбрать существующую, в выбранной сети нажмите кнопку Add element и перейдите на вкладку «join code».
На этом этапе ваш ESP32 и ваш ноутбук находятся в одной сети VLAN. Лучшая поддержка хоста – в веб-браузере Mozilla Firefox (другие браузеры также работают, но вы должны использовать IPv6-адрес вашего устройства, который вы найдете по адресу https://app.husarnet.com) и набрать: http://esp32template:8000, после этого вы должны увидеть веб-интерфейс для управления вашим ESP32.
#include <WiFi.h>
#include <WiFiMulti.h>
#include <WebSocketsServer.h>
#include <Husarnet.h>
#include <ArduinoJson.h>
#include "FS.h"
#include "SD.h"
#include <SPI.h>
#define HTTP_PORT 8000
#define WEBSOCKET_PORT 8001
const int BUTTON_PIN = 10;
const int LED_PIN = 11;
const int SDCS_PIN = 22;
typedef struct {
char ssid[30];
char pass[30];
} WifiNetwork;
struct WifiConfig {
WifiNetwork net[10];
};
struct HusarnetConfig {
char hostname[30];
char joincode[30];
};
WifiConfig wifi_conf;
HusarnetConfig husarnet_conf;
int wifi_net_no = 0;
WiFiMulti wifiMulti;
WebSocketsServer webSocket = WebSocketsServer(WEBSOCKET_PORT);
HusarnetServer server(HTTP_PORT);
StaticJsonBuffer<200> jsonBufferTx;
StaticJsonBuffer<100> jsonBufferRx;
StaticJsonBuffer<500> jsonBufferSettings;
JsonObject& rootTx = jsonBufferTx.createObject();
char* html;
bool wsconnected = false;
void onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
switch (type) {
case WStype_DISCONNECTED:
{
wsconnected = false;
Serial.printf("ws [%u] Disconnected\r\n", num);
}
break;
case WStype_CONNECTED:
{
wsconnected = true;
Serial.printf("ws [%u] Connection from Husarnet\r\n", num);
}
break;
case WStype_TEXT:
{
Serial.printf("ws [%u] Text:\r\n", num);
for (int i = 0; i < length; i++) {
Serial.printf("%c", (char)(*(payload + i)));
}
Serial.println();
JsonObject& rootRx = jsonBufferRx.parseObject(payload);
jsonBufferRx.clear();
uint8_t ledState = rootRx["led"];
Serial.printf("LED state = %d\r\n", ledState);
if (ledState == 1) {
digitalWrite(LED_PIN, HIGH);
}
if (ledState == 0) {
digitalWrite(LED_PIN, LOW);
}
}
break;
case WStype_BIN:
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
default:
Serial.printf("ws [%u] other event\r\n", num);
break;
}
}
void writeFile(fs::FS &fs, const char * path, const char * message);
void taskWifi( void * parameter );
void taskHTTP( void * parameter );
void taskWebSocket( void * parameter );
void taskStatus( void * parameter );
void setup()
{
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
SD.begin(SDCS_PIN);
if (!SD.begin(SDCS_PIN)) {
Serial.println("Attach SD card and restart");
return;
}
uint8_t cardType = SD.cardType();
switch (cardType) {
case CARD_NONE:
case CARD_UNKNOWN:
Serial.println("Attach SD card and restart");
return;
case CARD_SD:
Serial.println("SD card detected");
break;
case CARD_SDHC:
Serial.println("SDHC card detected");
break;
}
File file = SD.open("/index.htm");
if (!file) {
Serial.println("index.htm file doesn't exist. Save it on SD card and restart.");
return;
}
Serial.printf("index.htm file size: %d\r\n", file.size());
html = new char [file.size() + 1];
file.read((uint8_t*)html, file.size());
html[file.size()] = 0;
file.close();
file = SD.open("/settings.js");
if (!file) {
Serial.println("settings.js file doesn't exist. Save it on SD card and restart.");
return;
}
Serial.printf("settings.js file size: %d\r\n", file.size());
char* setting_json = new char [file.size() + 1];
file.read((uint8_t*)setting_json, file.size());
setting_json[file.size()] = 0;
file.close();
JsonObject& rootSettings = jsonBufferSettings.parseObject(setting_json);
String husarnet_hostname = rootSettings["husarnet"]["hostname"];
String husarnet_joincode = rootSettings["husarnet"]["joincode"];
strcpy (husarnet_conf.hostname, husarnet_hostname.c_str());
strcpy (husarnet_conf.joincode, husarnet_joincode.c_str());
Serial.println();
Serial.println("husarnet");
Serial.printf("hostname: %s\r\n", husarnet_conf.hostname);
Serial.printf("joincode: %s\r\n", husarnet_conf.joincode);
JsonArray& wifinetworks = rootSettings["wifi"];
wifi_net_no = wifinetworks.size();
Serial.println();
Serial.printf("number of Wi-Fi networks: %d\r\n", wifi_net_no);
if (wifi_net_no > 10) {
Serial.println("too many WiFi networks on SD card, 10 max");
wifi_net_no = 10;
}
for (int i = 0; i < wifi_net_no; i++) {
String ssid = wifinetworks[i]["ssid"];
String pass = wifinetworks[i]["pass"];
wifiMulti.addAP(ssid.c_str(), pass.c_str());
Serial.printf("WiFi %d: SSID: \"%s\" ; PASS: \"%s\"\r\n", i, ssid.c_str(), pass.c_str());
}
xTaskCreate(
taskWifi,
"taskWifi",
10000,
NULL,
1,
NULL);
xTaskCreate(
taskHTTP,
"taskHTTP",
10000,
NULL,
2,
NULL);
xTaskCreate(
taskWebSocket,
"taskWebSocket",
10000,
NULL,
1,
NULL);
xTaskCreate(
taskStatus,
"taskStatus",
10000,
NULL,
1,
NULL);
}
void taskWifi( void * parameter ) {
while (1) {
uint8_t stat = wifiMulti.run();
if (stat == WL_CONNECTED) {
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
Husarnet.join(husarnet_conf.joincode, husarnet_conf.hostname);
Husarnet.start();
server.begin();
while (WiFi.status() == WL_CONNECTED) {
delay(500);
}
} else {
Serial.printf("WiFi error: %d\r\n", (int)stat);
delay(500);
}
}
}
void taskHTTP( void * parameter )
{
String header;
while (1) {
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
HusarnetClient client = server.available();
if (client) {
Serial.println("New Client.");
String currentLine = "";
Serial.printf("connected: %d\r\n", client.connected());
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
header += c;
if (c == '\n') {
if (currentLine.length() == 0) {
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
client.println(html);
break;
} else {
currentLine = "";
}
} else if (c != '\r') {
currentLine += c;
}
}
}
header = "";
client.stop();
Serial.println("Client disconnected.");
Serial.println("");
} else {
delay(200);
}
}
}
void taskWebSocket( void * parameter )
{
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
webSocket.begin();
webSocket.onEvent(onWebSocketEvent);
while (1) {
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
webSocket.loop();
delay(1);
}
}
void taskStatus( void * parameter )
{
String output;
int cnt = 0;
uint8_t button_status = 0;
while (1) {
if (wsconnected == true) {
if (digitalRead(BUTTON_PIN) == LOW) {
button_status = 1;
} else {
button_status = 0;
}
output = "";
rootTx["counter"] = cnt++;
rootTx["button"] = button_status;
rootTx.printTo(output);
Serial.print(F("Sending: "));
Serial.print(output);
Serial.println();
webSocket.sendTXT(0, output);
}
delay(100);
}
}
void loop()
{
delay(5000);
}
© digitrode.ru