• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

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

Melandr

Member
Спасибо за пример и подсказки. А в коде у Вас есть комментарий "В корень SPIFFS надо загрузить веб приложение" - что подразумевается html с js ?
 

Melandr

Member
Добрый вечер!
Нашел пример использования websockets в асинхронном вебсервере
/*********
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp8266-nodemcu-websocket-server-arduino/
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*********/

// Import required libraries
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "DD-WRT";
const char* password = "gCU8YNZs";

bool ledState = 0;
const int ledPin = 2;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
h1 {
font-size: 1.8rem;
color: white;
}
h2{
font-size: 1.5rem;
font-weight: bold;
color: #143642;
}
.topnav {
overflow: hidden;
background-color: #143642;
}
body {
margin: 0;
}
.content {
padding: 30px;
max-width: 600px;
margin: 0 auto;
}
.card {
background-color: #F8F7F9;;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
padding-top:10px;
padding-bottom:20px;
}
.button {
padding: 15px 50px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
/*.button:hover {background-color: #0f8b8d}*/
.button:active {
background-color: #0f8b8d;
box-shadow: 2 2px #CDCDCD;
transform: translateY(2px);
}
.state {
font-size: 1.5rem;
color:#8c8c8c;
font-weight: bold;
}
</style>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
<div class="topnav">
<h1>ESP WebSocket Server</h1>
</div>
<div class="content">
<div class="card">
<h2>Output - GPIO 2</h2>
<p class="state">state: <span id="state">%STATE%</span></p>
<p><button id="button" class="button">Toggle</button></p>
</div>
</div>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
var state;
if (event.data == "0"){
state = "ON";
}
else{
state = "OFF";
}
document.getElementById('state').innerHTML = state;
}
function onLoad(event) {
initWebSocket();
initButton();
}
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
function toggle(){
websocket.send('toggle');
}
</script>
</body>
</html>
)rawliteral";

void notifyClients() {
ws.textAll(String(ledState));
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
}
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}

void initWebSocket() {
ws.onEvent(onEvent);
server.addHandler(&ws);
}

String processor(const String& var){
Serial.println(var);
if(var == "STATE"){
if (ledState){
return "ON";
}
else{
return "OFF";
}
}
}

void setup(){
// Serial port for debugging purposes
Serial.begin(115200);

pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);

// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}

// Print ESP Local IP Address
Serial.println(WiFi.localIP());

initWebSocket();

// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});

// Start server
server.begin();
}

void loop() {
ws.cleanupClients();
digitalWrite(ledPin, ledState);
}
Сравниваю с Вашим примером. Я так понимаю, что используя json можно передавать строку и обновлять показания на клиентах. Мне непонятно, в данном примере используется щелчок по кнопке в веб-интерфейсе, далее передается "toggle" на ESP. В ESP сравниваются полученные данные со строкой и выполняется инвертирование выхода светодиода. Но есть пару вопросов.
1. Если я буду передавать несколько значений, обработчик на ESP будет один, с разными условиями проверки полученных данных, точнее сначала распарсивания, а потом проверки?
2. Допустим у меня с ESP передается какое-то быстро меняющееся значение, меня интересует период обновления 500 мс. Значит для этой переменной мне необходим будет другой обработчик в коде ESP? Как задается период обновления данных?

Извините за глупые вопросы, но голова кругом идет от javascript, html и от других вебтехнологий.
ЗЫ: Также вопрос по JS - Есть функции инициализации кнопки и отработки клика по кнопке.
function initButton() {

document.getElementById('button').addEventListener('click', toggle);

}

function toggle(){

websocket.send('toggle');

}
Необходимо для каждой кнопки делать инициализацию и обработку клика или можно передавать в функцию элемент (кнопку), по которой щелкнули и взывать общий обработчик передавая ему id нажатой кнопки и в нем уже по id делать switch?
 

Melandr

Member
И еще вопрос. Для синхронного сервера есть библиотека WebSocketsServer.h - она аналогична плагину websockets для асинхронного вебсервера? Так как документации практически нет, либо смотреть по исходникам библиотеки, с минимумом комментариев на английском, либо разбираться по примерам реализации чужого кода. А примеров работы с вебсокетами для асинхронного вебсервера не особо много.
Правда непонятно в чем принципиальные преимущества асинхронного сервера над синхронным?
 

EvgeniyS

Member
И еще вопрос. Для синхронного сервера есть библиотека WebSocketsServer.h - она аналогична плагину websockets для асинхронного вебсервера? Так как документации практически нет, либо смотреть по исходникам библиотеки, с минимумом комментариев на английском, либо разбираться по примерам реализации чужого кода. А примеров работы с вебсокетами для асинхронного вебсервера не особо много.
Правда непонятно в чем принципиальные преимущества асинхронного сервера над синхронным?
Основное различие в том, что синхронный сервер работает в цикле loop и нужно следить чтобы HandleServer не блокировал работу Wifi, т.е. если у вас имеется внутри долгий по времени (более 40-50мс) обработчик, то надо будет прерывать его, вставляя в код yield() или delay(0) для нормальной работы wifi сети. Асинхронный сервер работает в своем отдельном цикле и самостоятельно прерывается для нормальной работы wifi сети. Ну и как по мне, ESPAsyncWebServer более богат функционалом и удобен в использовании. В любом случае, обе библиотеки - рабочий инструмент, так что выбор за вами.
 

EvgeniyS

Member
Набросал для вас небольшой сервер на вебсокетах для примера ссылка
Исходный код сервера:
C++:
#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){
    StaticJsonBuffer<200> jsonBuffer; // создаем буфер
    JsonArray& root = jsonBuffer.createArray(); //создаем ссылку на массив
    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 = root.measureLength(); // записываем длину массива
    AsyncWebSocketMessageBuffer * buffer = ws1.makeBuffer(len); // создаем буфер для отправки данных
    if (buffer) {
        root.printTo((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){
    StaticJsonBuffer<50> jsonBuffer;
    JsonArray& root = jsonBuffer.createArray();
    root.add(payload);
    root.add(ESP.getChipId());
    size_t len = root.measureLength();
    AsyncWebSocketMessageBuffer * buffer = ws1.makeBuffer(len);
    if (buffer) {
        root.printTo((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());
  DynamicJsonBuffer jsonBuffer;
  JsonArray& root = jsonBuffer.parseArray(data);
  if(root.success()){
    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);
  }
}

// Обработчик несуществующей страницы
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();
}
Веб морду написал на фреймворке (Svelte)

HTML:
<script>
import { onMount } from 'svelte';

const buttons = [0,2,4,5]
let states = [255,255,255,255]
let uptime = undefined
let timeIs = "unknown"
let showFreeHeap = false
let freeHeap = "unknown"
let online = false
let ws = ""
let chipInfo = undefined

onMount(() => { // WS-клиент + обработчик сообщений
                let startWs = () => {
            ws = new WebSocket('ws://' + document.location.host + '/ws', ['arduino']);
            //ws = new WebSocket('ws://192.168.4.1/ws');
            ws.onopen = e => online = true
            ws.onclose = e => online = false
            ws.onerror = e => online = false
            ws.onmessage = e => {
              console.log("from esp: ", e.data)
              let jsonStr = IsValidJSONString(e.data)
              if(jsonStr){
                switch (jsonStr[0]){
                  case 0:
                  states = jsonStr[1]
                  break
                  case 1:
                  freeHeap = jsonStr[1]
                  break
                  case 2:
                  chipInfo = jsonStr[1]
                  break
                  case 3:
                  uptime = jsonStr[1]
                  break
                }
              }
            }
          }
          let check = () => {
            if (!ws || ws.readyState != 1) {
                ws.close()
                online = false
                startWs()
            }
          }
          let IsValidJSONString =(str)=> {
            let jsonS
            try {
              jsonS = JSON.parse(str);
            } catch (e) {
              return false;
            }
            return jsonS;
          }
         
          startWs();
          setInterval(check, 5000);
        });

let bClick = (i)=>{
  let str = "[0,"+i+"]"
  ws.send(str);
}

let getChipId = ()=>{
  ws.send("[2,2]")
}

let toggleFreeHeap = ()=>{
  let str = "[1,["+showFreeHeap+"]]"
  ws.send(str)
}

$: {if(uptime != undefined){
    let d, h, m, s;
    s = Math.floor(uptime / 1000);
    m = Math.floor(s / 60);
    s = s % 60;
    h = Math.floor(m / 60);
    m = m % 60;
    d = Math.floor(h / 24);
    h = h % 24;
    h += d * 24;
    timeIs =  h + ':' + m + ':' + s;
  }else{
    timeIs = "unknown"
  }
}


</script>
<!--HTML----------------------------------------------------- -->
<header>
  <h1 class={online ? "c-green":"c-red"}>{online ? "online":"offline"}</h1>
  {#if online}
  <h1>Uptime:  {timeIs}</h1>
  {/if}
  {#if showFreeHeap}
    <h1>Free heap: {freeHeap} bytes</h1>
  {/if}
  <input type="checkbox" bind:checked={showFreeHeap} on:change={toggleFreeHeap} disabled={online ? false:true}>
  show free heap
</header><hr>
<main>
{#each buttons as b, i}
  <button class={(!online || states[i] == 255) ? "c-gray":(states[i] == 0) ? "c-red":"c-green"} on:click={()=>bClick(b)}>Gpio {b}</button><br>
{/each}
<hr>
{#if chipInfo == undefined}
  <button class="c-gray" on:click={getChipId}>Get chip ID</button>
{:else}
  <h1>ChipId: {chipInfo}</h1>
{/if}
</main>
<!--STYLE---------------------------------------------------- -->
<style>
:global(body){
    font-size: 26px;
    font-weight: bolder;
  }
button{
   margin: 10px;
   font-size: 32px;
   border-radius: 8px;
   font-weight: bolder;
   border: none;
}
input{

        width: 40px;
        height: 30px;
        font-size:26px;
        font-weight: bolder;
        white-space: pre;
}
.c-red{
   background-color: red;
}
.c-green{
   background-color: green;
}
.c-gray{
   background-color: silver;
}
</style>
JavaScript:
import App from './App.svelte';

const app = new App({
    target: document.body
});

export default app;
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width,initial-scale=1'>
    <title>example</title>
    <link rel='icon' type='image/png' href='/favicon.png'>
    <script defer src='/bundle.js'></script>
</head>

<body>
</body>
</html>
Проверил - работает.
П.С. Я не программист по профессии, у меня это на уровне хобби, поэтому написал как смог, надеюсь вам это поможет.
 

Melandr

Member
Доброй ночи! Спасибо за код. Правда с наскоку запустить не получилось, не создается файл bundle.js. Походу нужно установить svelte. Пока поставил node.js
 

Melandr

Member
Спасибо, запустил. получилось. А такой вопрос, попробовал скетч переделать под подключение к роутеру, но походу при компиляции в bundle.js попадает IP-адрес программной точки доступа 192.168.4.1 и соединение websocket не поднимается. Я попробовал установить svelte, но на втором этапе
npx degit sveltejs/template my-svelte-project
сыпет ошибки. Еще такой вопрос, почему то перестает обновляться информация на веб-странице, при этом управление выводом со страницы работает
 

vavanvanvanovich

New member
Добрый день, я новичок, и не могу понять как организовать подключение к esp8266 нескольких телефонов, 1 подключил через сокет, пробую подключиться с другого устройства так всё зависает
 

Melandr

Member
Добрый день, я новичок, и не могу понять как организовать подключение к esp8266 нескольких телефонов, 1 подключил через сокет, пробую подключиться с другого устройства так всё зависает
Делал примеры с асинхронным веб-сервером, пробовал подключаться со стационарного компьютера и телефона одновременно, все работает. По мануалам при реализации softAP на ESP возможно подключение 5 клиентов.
 

EvgeniyS

Member
Спасибо, запустил. получилось. А такой вопрос, попробовал скетч переделать под подключение к роутеру, но походу при компиляции в bundle.js попадает IP-адрес программной точки доступа 192.168.4.1 и соединение websocket не поднимается. Я попробовал установить svelte, но на втором этапе
npx degit sveltejs/template my-svelte-project
сыпет ошибки. Еще такой вопрос, почему то перестает обновляться информация на веб-странице, при этом управление выводом со страницы работает
Да, походу я накосячил и забыл раскоментить 1 строку и закометить другую.
ws = new WebSocket('ws://' + document.location.host + '/ws', ['arduino']);
//ws = new WebSocket('ws://192.168.4.1/ws');
 

Melandr

Member
EvgeniyS, а такой вопрос. Если я в скетче Ардуино изменю работу с json с версии 5 на версию 6, не нужно перекомпилировать файлы js?
PS: почему-то передача данных с ESP на страницу подвисает.
 

EvgeniyS

Member
EvgeniyS, а такой вопрос. Если я в скетче Ардуино изменю работу с json с версии 5 на версию 6, не нужно перекомпилировать файлы js?
Я не пробовал, но json 5 подключается в библиотеку сервера, поэтому скорее всего не получится собрать проект. На стороне клиента (js) проблем не будет json строка она и Африке json:)
 

Melandr

Member
соединение ws активно, но при нажатии кнопки не приходит ответ, пока страницу не обновишь
2021-03-18_141712.jpg
 

EvgeniyS

Member
Еще такой вопрос, почему то перестает обновляться информация на веб-странице, при этом управление выводом со страницы работает
Тоже заметил, пока не знаю почему, писал все это на "скорую руку". Первое что приходит на ум: заменить в loop yield() на delay(20), например
 
Сверху Снизу