• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

ESP8266 и несколько клиентов

Melandr

Member
Я не пробовал, но json 5 подключается в библиотеку сервера, поэтому скорее всего не получится собрать проект. На стороне клиента (js) проблем не будет json строка она и Африке json:)
Вот нашел мануальчик - JSON, попробую по нему переделать
 

EvgeniyS

Member
Можно добавить на сервер, а конкретно в слушатель
C++:
  } else if(type == WS_EVT_DISCONNECT){
    //client disconnected
    os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
  } else if(type == WS_EVT_ERROR){
    //error was received from the other end
    os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
  }
чтобы удостовериться что соединение не разорвалось на стороне сервера.
 

Melandr

Member
Тоже заметил, пока не знаю почему, писал все это на "скорую руку"
Ничего, спасибо и на этом, для разборки работы вебсокетов пойдет.
Кстати почему используете Svelte? Есть же Bootstrap или jQuery? Или это не то? Хотя смотрю я на код этих фреймворков, и он вроде бы проще по сравнению с чистым JS, но все равно смотрю на код js и тяжко пониманию.
 

EvgeniyS

Member
Ничего, спасибо и на этом, для разборки работы вебсокетов пойдет.
Кстати почему используете Svelte? Есть же Bootstrap или jQuery? Или это не то? Хотя смотрю я на код этих фреймворков, и он вроде бы проще по сравнению с чистым JS, но все равно смотрю на код js и тяжко пониманию.
Svelte - не совем обычный фреймворк, он работает как компилятор, т.е. анализирует код и переводит его в javascript, при этом не копируя себя самого в сборку, как это делают остальные фреймворки. В результате мы получаем сравнительно небольшой bundle.js, который можно кешировать и все это снижает нагрузку на SPIFFS и работает оно быстрее.
 

vavanvanvanovich

New member
Делал примеры с асинхронным веб-сервером, пробовал подключаться со стационарного компьютера и телефона одновременно, все работает. По мануалам при реализации softAP на ESP возможно подключение 5 клиентов.
Делал примеры с асинхронным веб-сервером, пробовал подключаться со стационарного компьютера и телефона одновременно, все работает. По мануалам при реализации softAP на ESP возможно подключение 5 клиентов.
я нашел примеры но это всё под страницы в браузере с вставленными HTML, у меня своё приложение на андроиде, и не могу сообразить как это всё сделать? или же AsyncWebServer он и предусмотрен на браузер только?
 

Melandr

Member
После установки Git компиляция прошла успешно. Правда не могу понять. Делал по инструкции отсюда https://tproger.ru/articles/introduction-to-svelte-3/
А если использовать Ваш исходник. Как правильно компилировать:
1. Создать каталог проекта.
2. Забросить в эту папку файлы проекта
index.htm
main.js
app.svelte
3. Запустить сборку командой
npm install
Правильно?
 

EvgeniyS

Member
Добавил на гугл диск полный исходник со всеми библиотеками и настройками (ссылка та же) папка "primer" Я там немного перенастроил кое-что, поэтому объяснять долго. Вам нужно будет скопировать папку в любое место, зайти в терминал и сразу можно пользоваться командами npm run build или npm run dev. По идее должно работать.
 

Melandr

Member
Добрый день EvgeniyS!
Попробовал переделать Ваш код под библиотеку json версии 6
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#define NUM_PTR_FUN 3

const uint8_t interval_1 = 50; //ms.
const uint16_t interval_2 = 500; //ms.
uint32_t timer_1 = 0;
uint32_t timer_2 = 0;
uint32_t freeHeap = 0;
bool sendFreeHeap = false;
bool needToUpdateStates = false;
IPAddress ipAP{192, 168, 4, 1};
AsyncWebServer server(80);
AsyncWebSocket ws1("/ws");
// Отправка состояний кнопок (GPIO)
void sendStates(AsyncWebSocketClient * client = NULL) {
StaticJsonDocument<200> jsonDoc; // создаем буфер
JsonArray root = jsonDoc.to<JsonArray>(); //создаем ссылку на массив
root.add(0); // добавляем элемент в корневой массив
JsonArray states = root.createNestedArray(); // создаем ссылку на вложенный массив
states.add(digitalRead(0)); // и добавляем туда данные
states.add(digitalRead(2));
states.add(digitalRead(4));
states.add(digitalRead(5));
size_t len = measureJson(root); // записываем длину массива
AsyncWebSocketMessageBuffer * buffer = ws1.makeBuffer(len); // создаем буфер для отправки данных
if (buffer) {
serializeJson(root, (char *)buffer->get(), len + 1); // если буфер успешно создан то записывем туда данные
if (client) {
client->text(buffer); // если клиент указан то отправляем содержимое буфера клиенту
} else {
ws1.textAll(buffer); // если клиент не указан то отправляем содержимое буфера всем подключеным клиентам
}
}
}
// Обработчик управления GPIO с веб интерфейса
void H_pinChange(JsonVariant payload, AsyncWebSocketClient * client) {
digitalWrite(payload, !digitalRead(payload));
needToUpdateStates = true;
}
// Обработчик управления free heap с веб интерфейса
void H_freeHeap(JsonVariant payload, AsyncWebSocketClient * client) {
sendFreeHeap = payload;
}
// Обработчик управления получения ID с веб интерфейса
void H_getChipId(JsonVariant payload, AsyncWebSocketClient * client) {
StaticJsonDocument<50> jsonDoc;
JsonArray root = jsonDoc.to<JsonArray>();
root.add(payload);
root.add(ESP.getChipId());
size_t len = measureJson(root);
AsyncWebSocketMessageBuffer * buffer = ws1.makeBuffer(len);
if (buffer) {
serializeJson(root, (char *)buffer->get(), len + 1);
client->text(buffer);
}
}

// массив указателей на функции обработчиков
void (*arrFnPtr[NUM_PTR_FUN])(JsonVariant payload, AsyncWebSocketClient * client) = {
H_pinChange, // [0] - управление GPIO
H_freeHeap, // [1] - отправлять free heap (да\нет)
H_getChipId // [2] - получить ID
// дальше можно дописывать для добавления новых обработчиков, при этом необходимо увеличить NUM_PTR_FUN до нужного размера
};

// Парсер входящих ws сообщений
// Число в первой ячейке массива(root[0]), является индексом указателя на функцию
// таким образом парсер определяет какую функцию-обработчик использовать
void readJsonData(const char *data, AsyncWebSocketClient * client) {
Serial.printf("from ws data: %s client: %u\n", data, client->id());
DynamicJsonDocument jsonDoc(1024);
auto error = deserializeJson(jsonDoc, data);
JsonArray root = jsonDoc.as<JsonArray>();
if (!error) {
arrFnPtr[root[0].as<uint8_t>()](root[1].as<JsonVariant>(), client);
}
}

// Приемник ws сообщений (упрощенный на 1 фрейм)
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.printf("client id: %u connected\n", client->id());
sendStates(client);
} else if (type == WS_EVT_DATA) {
AwsFrameInfo * info = (AwsFrameInfo*)arg;
data[info->len] = '\0';
readJsonData((char*)data, client);
} else if (type == WS_EVT_DISCONNECT) {
//client disconnected
os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if (type == WS_EVT_ERROR) {
//error was received from the other end
os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
}
}

// Обработчик несуществующей страницы
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
// Инициализация сервера
void serverInit() {
ws1.onEvent(onWsEvent);
server.addHandler(&ws1);
server.serveStatic("/", SPIFFS, "/").setCacheControl("max-age=31536000");
server.onNotFound(notFound);
server.begin();
}

// Инициализация wifi сети
void wifiInit() {
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(ipAP, ipAP, IPAddress(255, 255, 255, 0));
WiFi.softAP("espAP", "");
}

void setup() {
Serial.begin(115200);
Serial.println();
pinMode(0, OUTPUT);
pinMode(2, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
SPIFFS.begin();
wifiInit();
serverInit();
}

void loop() {
if (millis() > timer_1) {
timer_1 = millis() + interval_1;
if (needToUpdateStates) {
sendStates();
needToUpdateStates = false;
}
}
yield();
if (millis() > timer_2) {
timer_2 = millis() + interval_2;
ws1.printfAll("[3,%lu]", millis());
if (sendFreeHeap) {
freeHeap = ESP.getFreeHeap();
ws1.printfAll("[1,%u]", freeHeap);
}
}
yield();
}
Вроде бы работает.
 

EvgeniyS

Member
Добрый день EvgeniyS!
Попробовал переделать Ваш код под библиотеку json версии 6
Вроде бы работает.
Ну и хорошо. В парсере можно попробовать сделать StaticJsonDocument вместо динамического, чтобы память выделялась на стеке а не в куче, это ускорит работу парсера.
Также в обработчике получения ID можно весь код заменить на:
Код:
client->printf("[2,%u]", ESP.getChipId());
Я специально написал в разных вариациях для примера, но простые короткие строки можно отправлять printf-ом
 

Melandr

Member
Ну и хорошо. В парсере можно попробовать сделать StaticJsonDocument вместо динамического, чтобы память выделялась на стеке а не в куче, это ускорит работу парсера.
Также в обработчике получения ID можно весь код заменить на:
Код:
client->printf("[2,%u]", ESP.getChipId());
Я специально написал в разных вариациях для примера, но простые короткие строки можно отправлять printf-ом
Кстати, я так и не понял, зачем объявлять динамический объект с фиксированным размером
 

EvgeniyS

Member
Кстати, я так и не понял, зачем объявлять динамический объект с фиксированным размером
ArduinoJson, начиная с 6 версии это по сути новая библиотека а не дописанная старая. Там автор много что перекроил. Где-то он объяснял, почему так сделал но я не помню где.
 

vavanvanvanovich

New member
Всем привет, вроде начинаю понемногу разбираться но очень медленно. Использую AsyncWebSocketServer, и вот код
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client,
AwsEventType type, void * arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
Serial.println("Подключили сокет");
}else if(type == WS_EVT_DISCONNECT){
Serial.println("КЛИЕНТ ОТКЛЮЧЕН!!");
не могу понять как data прочитать и условие на выходы поставить???
 

Melandr

Member
Выше EvgeniyS выложил рабочий пример, отталкивайтесь от него.
Данные читаются в секции
} else if (type == WS_EVT_DATA) {
AwsFrameInfo * info = (AwsFrameInfo*)arg;
data[info->len] = '\0';
readJsonData((char*)data, client);
 

Melandr

Member
Добавил на гугл диск полный исходник со всеми библиотеками и настройками (ссылка та же) папка "primer" Я там немного перенастроил кое-что, поэтому объяснять долго. Вам нужно будет скопировать папку в любое место, зайти в терминал и сразу можно пользоваться командами npm run build или npm run dev. По идее должно работать.
Добрый день! Ваш пример нормально компилируется, но все равно пытается подключаться к 192.168.4.1
Хотя ESP подключена к роутеру.
Строка
ws = new WebSocket('ws://' + document.location.host + '/ws', ['arduino']);
//ws = new WebSocket('ws://192.168.4.1/ws');
закомментирована
Страница html загружается в браузере на клиенте, но вебсокет соединение не поднимается. При этом при настройке программной точки доступа и загрузке полного bundle.js все работает.
 

EvgeniyS

Member
Вероятно, вам нужно зайти в режим разработчика в браузере, отключить кэширование и обновить страницу, затем можно обратно включить.
 

Melandr

Member
Кстати, когда подвисает обновление информации на вебстранице, после обновления страницы показания начинают идти дальше, ESP не перезагружается, но почему то данные на страницу не идут. При этом в последовательном мониторе информации об разрыве вебсокет - нет
 

vavanvanvanovich

New member
После долгих стараний я подключил websocket и asyncwebserver, все работает нормально, но в момент перезагрузки сервера кнопка клиента просто не реагирует, есть может какое-то правило?
 

Melandr

Member
Доброй ночи!
В HTML и JS не сильно разбираюсь, но почему-то не выводит время форматировано в строке
<p class="reading">Время в часах: <span id="time"></span></p>
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="icon" href="data:,">
<link rel="stylesheet" href="/style.css">
<title>Контроллер насоса котла</title>

</head>
<body>
<div class="topnav">
<h2>Контроллер насоса котла</h2>
</div>
<p class="reading">Период: <span id="period">%PERIOD%</span> мс</p>
<p class="reading">Время работы: <span id="uptime">%UPTIME%</span> сек</p>
<p class="reading">Время в часах: <span id="time"></span></p>
<p><span id="dimmerValue">%DIMMERVALUE%</span> &percnt;</p>
<p>
<button class="button1" onclick="decreaseDimmerValue()">-</button>
<input type="range" onchange="updateSliderDimmer(this)" id="dimmerSlider" min="0" max="100" value="%DIMMERVALUE%" step="1" class="slider2">
<button class="button2" onclick="increaseDimmerValue()">+</button>
</p>
<p><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="output" %BUTTONPLACEHOLDER%><span class="slider"></span></label></p>
<script src="/server.js"></script>
</body>
</html>
var timeFormat = (function (){
function num(val){
val = Math.floor(val);
return val < 10 ? '0' + val : val;
}

return function (ms/**number*/){
var sec = ms / 1000
, days = sec / (60 * 60 * 24)
, hours = sec / 3600 % 24
, minutes = sec / 60 % 60
, seconds = sec % 60
;

return Math.floor(days) + ' дней ' + num(hours) + ":" + num(minutes) + ":" + num(seconds);
};
})();

var time;

function decreaseDimmerValue() {
var sliderValue = document.getElementById("dimmerSlider").value;
sliderValue--;
document.getElementById("dimmerSlider").value = sliderValue;
document.getElementById("dimmerValue").innerHTML = sliderValue;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/slider?value="+sliderValue, true);
xhr.send();
}
function increaseDimmerValue() {
var sliderValue = document.getElementById("dimmerSlider").value;
sliderValue++;
document.getElementById("dimmerSlider").value = sliderValue;
document.getElementById("dimmerValue").innerHTML = sliderValue;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/slider?value="+sliderValue, true);
xhr.send();
}
function toggleCheckbox(element) {
var xhr = new XMLHttpRequest();
if(element.checked){ xhr.open("GET", "/update?state=1", true); console.log("button - нажата");}
else { xhr.open("GET", "/update?state=0", true);console.log("button - отжата");}
xhr.send();
}
function updateSliderDimmer(element) {
var sliderValue = document.getElementById("dimmerSlider").value;
document.getElementById("dimmerValue").innerHTML = sliderValue;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/slider?value="+sliderValue, true);
xhr.send();
console.log("Скорость: " + sliderValue);
}
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('uptime', function(e) {
console.log("uptime", e.data);
document.getElementById("uptime").innerHTML = e.data;
time = e.data;
document.getElementById("time").innerHTML = timeFormat(time);
}, false);
source.addEventListener('currentSpeed', function(e) {
console.log("currentSpeed", e.data);
document.getElementById("dimmerSlider").value = e.data;
document.getElementById("dimmerValue").innerHTML = e.data;
}, false);
source.addEventListener('period', function(e) {
console.log("period", e.data);
document.getElementById("period").innerHTML = e.data;
}, false);
source.addEventListener('motorState', function(e) {
console.log("motorState", e.data);
var inputChecked;
if( e.data == 1){ inputChecked = true; }
else { inputChecked = false; }
document.getElementById("output").checked = inputChecked;
}, false);
}
 

Melandr

Member
Разобрался, торможу. Видать болячка плохо влияет, я ж время клиенту уже в секундах бросаю :)
 
Сверху Снизу