Базовый WEB сервер
- Войдите на сайт для отправки комментариев
Основная идея была очень простая: реализовать отдельный модуль который будет общаться с основной платой по UART и отрабатывать как WEB сервер, в случае если он зависнет - его сможет перезапустить основная плата, и если зависнет основная плата и можно будет удаленно сделать ребут.
Теперь функционал к которому я стремлюсь
1. Все функциональные страницы сделаны в виде нормальных файлов и лежат на флеше, то есть внешний вид можно отлаживать на любом стандартном WEB сервере запущенном на локальном компьютере. (исключение является сборка Ajax ответа)
2. поддержка основных сервисных страниц (404 и еще некоторых)
3. многопоточность (минимум на 3 одновременных сессии с разных компьютеров)
4. некая "авторизация", с учетом того, что все-же это только для внутренней сети, то довольно упрощенная.
собственно сам проект выложу в первом посте (по сколько его можно менять и добавлять что-то)
Проект тестировался на ESP-01s
Начну с WEB морды, выглядит пока так
файл params.h
#ifndef GLOBAL_PARAMS_H #define GLOBAL_PARAMS_H #include <ESP8266WiFi.h> // количество параметров #define PARAM_COUNT 15 // количество символов в ID параметра #define PARAM_ID_COUNT 20 // количество символов в значении параметра value-1 #define PARAM_VALUE_COUNT 20 // количество символов в статусе параметра value-2 #define PARAM_STATUS_COUNT 20 // количество символов в параметре CONTENT_COUNT #define PARAM_CONTENT_COUNT 5 #define BUFER_COUNT 250 // структура данных для обработки одного клиента, или uart или файла настроек struct t_params { // буферы, нужны для реализации асинхронности чтения данных в главном цикле char buf[BUFER_COUNT + 1]; // текущая заполненая длинна данных, от 0 до BUFER_COUNT uint8_t tec_size; // имя сервера String Request_Host; // GET, POST String Request_Type; // строка запроса, начинается с / String Request; // признак получения завершения строки, 2 байта // 3328 - 0x0d, 10 - 0x0a, 3338 - 0x0d0a uint16_t Request_EOF; // количество символов в данных uint16_t Request_Content_length; // флаг того, что лежит в буфере buf_wifi // 0 - ожидаем заголовок // 1 - ожидаем данные // 2 - ожидаем исполнения uint16_t Request_Flag; // время последней операции с клиентом unsigned long Request_time_loop; // время последнего окончания чтения первой строки запроса unsigned long time_loop_new; // буферы для хранения текущего считываемого параметра char value_tec[PARAM_VALUE_COUNT+1]; char status_tec[PARAM_STATUS_COUNT+1]; char content_length_tec[PARAM_CONTENT_COUNT+1]; // указатель на перый элемент списка out_id который нам подходит uint8_t min_pointer; // указатель на последний элемент списка out_id который нам подходит uint8_t max_pointer; // указатель на найденный элемент списка out_id, по которомы мы ччитаем value и status uint8_t tec_pointer; // номер символа в читаемом параметре ID, value,status uint8_t num_char; // шаг на котором мы при получении данных. // 0 - ждем начала блока "<" // 1 - читаем ID, и ждем "=" // 2 - читаем value, и ждем ";" // 3 - читаем status, и ждем ">" // 4 - все верно прочитано, можно копировать в память uint8_t step; // режим авторизации для ajax_set_data // 0 - ошибка или пусто // 1 - получен пароль, нужно отправить подтверждение и идентификатор // 2 - получен идентификатор, отправляем только идентификатор uint8_t autorization; }; // структура данных для обработки одного клиента struct t_wifi_clients { WiFiClient client; t_params p; }; struct t_out { // идентификатор char id[PARAM_ID_COUNT+1]; // длинна идентификатора в значащих символах uint8_t length; // битовый флаг // бит 1 - данные обмениваются с UART // бит 2 - данные обмениваются с WiFi // бит 3 - данные обмениваются с SD uint8_t flag; }; // эти ID используются в HTML и передаются от контроллера по UART // дополнительно сюда добавляем ID элементов которые приходят из WiFi // дополнительно добавляем переменные которые определяются в этом модуле // и отправляются или в UART или WiFi или инициируются из файла setup.ini // ВНИМАНИЕ !!! данный список должен быть отсортирован по ID // часть этого списока хранится во flash памяти (3 бит третьего параметра) // ВНИМАНИЕ !!! при изменении списка проверь именные указатели именного доступа !!!!!!! const t_out out_id[PARAM_COUNT] = { {"admin\0", 5, 0 + 0 + 4}, // - пароль для изменения параметров через web {"data\0", 4, 1 + 2 + 0}, // - текущая дата {"level\0", 5, 1 + 2 + 0}, // - уровень жидкости {"m_water\0", 7, 1 + 2 + 0}, // - насос {"pass\0", 4, 0 + 0 + 4}, // - пароль - WiFi подключения {"res_cpu\0", 7, 0 + 2 + 0}, // - условная загрузка главного цикла от 0 до 100 (100 - возможны критические проблеммы) {"res_mem\0", 7, 0 + 2 + 0}, // - условная загрузка памяти 0 до 100 (100 - возможны критические проблеммы) {"res_uart\0", 8, 0 + 2 + 0}, // - получение данных с UART // 100 - возможена потеря данных // 0 - абонент "завис" {"res_wifi\0", 8, 0 + 2 + 0}, // - условная загрузка сети 0 до 100 // 100 - возможен возврат HTML кода 503 и пропуск данных // 0 - возможено клиент "завис" {"serial\0", 5, 0 + 0 + 4}, // - скорость обмена по серийному порту {"sid\0", 3, 0 + 0 + 4}, // - sid - WiFi подключения {"status\0", 6, 0 + 2 + 0}, // - контроль того, что данные меняются и основной контроллер работает {"t_time_end\0", 10,1 + 2 + 4}, // - начало разрешенного периода {"t_time_start\0", 12,1 + 2 + 4}, // - окончание разрешенного периода {"time\0", 4, 1 + 2 + 0} // - текущее время }; char out_value[PARAM_COUNT][PARAM_VALUE_COUNT+1]; char out_status[PARAM_COUNT][PARAM_STATUS_COUNT+1]; // указатели для именного доступа к значениям char* admin = &(out_value[0][0]); char* pass = &(out_value[4][0]); char* res_cpu = &(out_value[5][0]); char* res_mem = &(out_value[6][0]); char* res_uart = &(out_value[7][0]); char* res_wifi = &(out_value[8][0]); char* speedOfLight = &(out_value[9][0]); char* sid = &(out_value[10][0]); // ------------------------------------------------ void set_byte_status_tec (t_params* par, uint8_t num, uint8_t value) { if (num >= PARAM_STATUS_COUNT) { return; } else { par->status_tec[num] = value; } } void set_byte_value_tec (t_params* par, uint8_t num, uint8_t value) { if (num >= PARAM_VALUE_COUNT) { return; } else { par->value_tec[num] = value; } } // ------------------------------------------------ void read_0_31(t_params* par){ par->step = 0; } // ------------------------------------------------ void read_param_step_0(t_params* par, char c){ if (c != '<') { return; } par->step = 1; par->num_char = 0; par->tec_pointer = 0; par->min_pointer = 0; par->max_pointer = (uint8_t)(PARAM_COUNT)-1; par->value_tec[0] = 0; par->status_tec[0] = 0; par->content_length_tec[0] = 0; } // ------------------------------------------------ void read_param_step_1(t_params* par, char c){ if (par->num_char >= PARAM_ID_COUNT){ return; } // ищем идентификатор uint8_t fl = 0; uint8_t fl_end = par->max_pointer; for (uint8_t i = par->min_pointer; i <= par->max_pointer; i++) { if ((par->num_char > 0) && (out_id[i].id[par->num_char] == 0) && (c == '=')) { // мы нашли нужный id par->tec_pointer = i; par->num_char = 0; par->step = 2; break; } else if (out_id[i].length < par->num_char) { // слишком короткий ключ } else if (out_id[i].id[par->num_char] == c) { // пока вроде подходит fl_end = i; if (fl == 0) { par->min_pointer = i; fl = 1; } } else if (out_id[i].id[par->num_char] > c) { // уже стало больше break; } } if (par->step == 1) { if (fl == 0) { // ни один вариант не сработал par->step = 0; } else { par->max_pointer = fl_end; par->num_char = par->num_char+1; } } } // ------------------------------------------------ void read_param_step_2(t_params* par, char c){ // читаем VALUE if (par->num_char >= PARAM_VALUE_COUNT) { par->num_char = PARAM_VALUE_COUNT; } else { set_byte_value_tec (par, par->num_char, c); } if (c == ';') { set_byte_value_tec (par, par->num_char, 0); par->step = 3; par->num_char = 0; } else if (c == '>') { set_byte_value_tec (par, par->num_char, 0); par->step = 4; par->num_char = 0; } else { par->num_char = par->num_char+1; } } // ------------------------------------------------ void read_param_step_3(t_params* par, char c){ // читаем STATUS if (par->num_char >= PARAM_STATUS_COUNT) { par->num_char = PARAM_STATUS_COUNT; } else { set_byte_status_tec (par, par->num_char, c); } if (c == '>') { set_byte_status_tec (par, par->num_char, 0); par->step = 4; par->num_char = 0; } else { par->num_char = par->num_char+1; } } // ------------------------------------------------ void read_param_step_4(t_params* par){ par->step = 0; memcpy(&(out_value[par->tec_pointer][0]), &(par->value_tec[0]), PARAM_VALUE_COUNT+1); memcpy(&(out_status[par->tec_pointer][0]), &(par->status_tec[0]), PARAM_STATUS_COUNT+1); } #endifфайл index.html
<!DOCTYPE html> <html> <head> <link rel='icon' href='data:;base64,='> <meta charset="UTF-8"/> <meta http-equiv="Cache-Control" content="no-cache"> <link rel="stylesheet" type="text/css" href="main.css" /> <title>Web Home 259 Page</title> <script src="main.js"></script> </head> <body onload='GetData_H()'> <div class="s"> <div> <div class="fon"><div></div></div> <div class="time1 set"><div id="t_time_start"><p name="value" data-v="12:30">c: </p></div></div> <div class="time1 set"><div id="t_time_end"><p name="value" data-v="22:15">по: </p></div></div> <div class="fon"><div></div></div> <div class="resurse"> <div class="meter" id="res_cpu"><span name="value" data-v="20"></span><p name="value" data-v="20">CPU: </div> <div class="meter" id="res_mem"><span name="value" data-v="30"></span><p name="value" data-v="30">MEM: </div> <div class="meter" id="res_uart"><span name="value" data-v="100"></span><p name="value" data-v="100">UART: </div> <div class="meter" id="res_wifi"><span name="value" data-v="0"></span><p name="value" data-v="0">WIFI: </div> </div> </div> <div> <div class="tube red"><div name="left-bottom"></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> </div> <div> <div class="tube blue"><div name="top-bottom"></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> </div> <div> <div class="tube blue"><div name="top-bottom"></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> </div> <div> <div class="tube blue"><div name="top-bottom"></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> </div> <div> <div class="tube blue"><div name="top-bottom"></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> </div> <div> <div class="tube blue"><div name="top-bottom"></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> </div> <div> <div class="tube blue"><div name="top-right"></div></div> <div class="pump"><div id="m_water"><p name="status"></p></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> <div class="fon"><div></div></div> </div> <div class="decoration" style="--w:5;--h:1; --l:0; --t:0; "><p>Контроль</p></div> <div class="decoration tank" style="--w:4;--h:6; --l:1; --t:2"><p name="value">Колодец: </p><div id="level" name="value"></div></div> <div class="set_forms" id="forms_edit" data-f="off" > <p name="value">Изменение параметра: </p> <input type="text" autocomplete="off" id="forms_edit_i"> <button type="button" id="forms_edit_ok">OK</button> <button type="button" id="forms_edit_close">X</button> <br> <div class="set_pass" id="forms_edit_s"> <p>Идентификация: </p> <input type="password" autocomplete="off" id="forms_edit_p"> </div> </div> </div> <script> reg_set('t_time_start','open'); reg_set('t_time_end','open'); reg_set('forms_edit_ok','send'); reg_set('forms_edit_close','close'); </script> </table> </body> </html>файл main.css
@charset "windows-1251"; /* CSS Document */ :root { --Step: 75px; --ColorFon: #ffffff; } .s { position:relative; width:calc(var(--Step) * 5); height:calc(var(--Step) * 8); top:10px; left:10px; overflow:hidden; } .fon > div { float:left; overflow:hidden; width:var(--Step); height:var(--Step); } .set_pass[data-f="on"] { visibility: visible } .set_pass[data-f="off"] { visibility: hidden; } /* ---------------------------------------------- */ .set_forms { overflow:hidden; position: absolute; width: 100%; height: 100%; z-index: 1000; background-color: #99FFCC; visibility: hidden; } .set_forms[data-f="on"] { visibility: visible } .set_forms[data-f="off"] { visibility: hidden; } .set_forms > div{ overflow:hidden; position: absolute; width: 100%; height: 100%; z-index: 1000; background-color: #99FFCC; /* visibility: hidden; visibility: visible margin-right: 10px; */ } .set_forms > button{ cursor: pointer; } .set_forms #forms_edit_close{ margin-right: 5px; float: right; } /* ---------------------------------------------- */ .resurse { display: flow-root; } /* ---------------------------------------------- */ .meter > span { height: 100%; margin-right: 20px; display: block; background-color: rgb(43,194,83); overflow: hidden; border-radius: 8px; margin-right: calc(100% / 100 * (100 - var(--value))) } .meter { --value: 0; float: left; height: calc(var(--Step) / 4 - 2px); width: calc(100% - 2px); background: #c7ffed; border-radius: 8px; padding: 1px; box-shadow: inset 0 -1px 1px rgba(255,255,255,0.3); } /* сюда вставляем все возможные варианты значения индикатора data-v */ .meter [data-v="100"] { --value: 100; background: #fda9b1; } .meter [data-v="90"] { --value: 90; background: #fda9b1; } .meter [data-v="80"] {--value: 80; } .meter [data-v="70"] {--value: 70; } .meter [data-v="60"] {--value: 60; } .meter [data-v="50"] {--value: 50; } .meter [data-v="40"] {--value: 40; } .meter [data-v="30"] {--value: 30; } .meter [data-v="20"] {--value: 20; } .meter [data-v="10"] {--value: 10; } .meter p[name=value]{ text-align: center; margin-left: 0px; margin-top: -16px; font-size: 75%; display: block; height: 100%; border-radius: 8px; position: relative; overflow: hidden; } .meter p[name=value]:after{ content: attr(data-v); } /* ---------------------------------------------- */ .tank > div { --value: 0; overflow:hidden; position: absolute; left:2px; top:20px; width:calc(100% - 4px); height:calc(100% - 2px); background-color: #B3FFF2; background-repeat: no-repeat; background-position: left bottom; border-top: calc((var(--Step) * var(--h) - 2px) - ((var(--Step) * var(--h) - 2px - 2px) * var(--value) / 100 )) solid var(--ColorFon); } /* сюда вставляем все возможные варианты значения заполнения бака data-v */ .tank > div[data-v="100"] { --value: 100; background-color: #CC6600; } .tank > div[data-v="90"] { --value: 90; background-color: #FFAA80; } .tank > div[data-v="80"] { --value: 80; background-color: #80AAFF; } .tank > div[data-v="70"] {--value: 70; } .tank > div[data-v="60"] {--value: 60; } .tank > div[data-v="50"] {--value: 50; } .tank > div[data-v="40"] {--value: 40; } .tank > div[data-v="30"] {--value: 30; } .tank > div[data-v="20"] {--value: 20; } .tank > div[data-v="10"] { --value: 10; background-color: #FF0000; } .tank p[name=value]{ margin-left: 5px; } .tank p[name=value]:after{ content: attr(data-v); } /* ---------------------------------------------- */ .decoration { position: absolute; left:calc(var(--Step) * var(--l)); top:calc(var(--Step) * var(--t)); float:left; overflow:hidden; width:calc(var(--Step) * var(--w) - 2px); height:calc(var(--Step) * var(--h) - 2px); border: 1px dashed red; z-index: -1; } .decoration > p { margin-left: 5px; margin-top: 5px; font-weight: bold; color: #283593; } /* ---------------------------------------------- */ .tube.red > div { background-image: url(tube_red.png); } .tube.blue > div { background-image: url(tube_blue.png); } .tube > div { --Top: 34px; --Bottom: 34px; --Left: 34px; --Right: 34px; float:left; overflow:hidden; background-image: url(tube.png); background-repeat: no-repeat; border-top: var(--Top) dashed var(--ColorFon); background-position-y: calc(var(--Top) * (-1)); border-left: var(--Left) dashed var(--ColorFon); background-position-x: calc(var(--Left) * (-1)); border-bottom: var(--Bottom) dashed var(--ColorFon); border-right: var(--Right) dashed var(--ColorFon); height: calc(var(--Step) - var(--Top) - var(--Bottom)); width: calc(var(--Step) - var(--Left) - var(--Right)); } .tube div[name*="top"] { --Top: 0px; } .tube div[name*="bottom"] { --Bottom: 0px; } .tube div[name*="left"] { --Left: 0px; } .tube div[name*="right"] { --Right: 0px; } /* ---------------------------------------------- */ .pump > div { float:left; overflow:hidden; width:var(--Step); height:var(--Step); background-image: url(pump.png); background-repeat: no-repeat; background-position: center center; } .pump > div[data-st=ON] { background-image: url(pump_on.png); } .pump > div[data-st=OFF] { background-image: url(pump_off.png); } .pump > div[data-st^=E] { background-image: url(pump_off.png); } .pump p{ text-align: center; margin-top: 0px; } .pump p[name=status]:after { content: attr(data-st); } /* ---------------------------------------------- */ .time1 > div { float:left; overflow:hidden; width:var(--Step); height:var(--Step); } .time1 p{ margin-left: 5px; } .time1 p[name=value]{ margin-left: 5px; margin-top: 35px; } .time1 p[name=value]:after{ content: attr(data-v); } /* ---------------------------------------------- */ .set { cursor: pointer; } .set:hover { }файл main.js
// формат: {"t_home_1":{"value":{"data-v":"030"},"status":{"data-st":"OK"}}} function GetData_H() { var request = new XMLHttpRequest(); request.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var vdat = JSON.parse(this.responseText); if (vdat !== null && typeof vdat === 'object') { for (var key_id in vdat) { el = document.getElementById(key_id); if (el != null) { var vdat1 = vdat[key_id]; try {for (var key_id1 in vdat1) { let blocks = el.querySelectorAll('[name='+key_id1+']'); for( let i = 0; i < blocks.length; i++){ let vdat2 = vdat1[key_id1]; for (var key_id2 in vdat2) { blocks[i].setAttribute(key_id2, vdat2[key_id2]); el.setAttribute(key_id2, vdat2[key_id2]);}} }}catch (e) { }}}}}} request.open('POST', 'ajax_data', true); request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); request.send(null); setTimeout('GetData_H()', 5000);} function open_set(el_op) { el = document.getElementById('forms_edit'); if (el != null) { el.setAttribute('data-f','on'); } el = document.getElementById('forms_edit_i'); if (el != null) { el.value=el_op.getAttribute('data-v'); el.name=el_op.id; } el = document.getElementById('forms_edit_s'); if (el != null) { crc = sessionStorage.getItem('crc'); if (crc != null) { el.setAttribute('data-f','off'); } else { el.setAttribute('data-f','on'); } } } function close_set(el_op) { el = document.getElementById('forms_edit'); if (el != null) { el.setAttribute('data-f','off')} el = document.getElementById('forms_edit_s'); if (el != null) { el.setAttribute('data-f','off')} } function send_set(el_op) { var request = new XMLHttpRequest(); request.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var vdat = JSON.parse(this.responseText); if (vdat !== null && typeof vdat === 'object') { if (vdat.set == 'ok' && vdat.crc !== null && typeof vdat.crc !== 'undefined') { sessionStorage.setItem('crc', vdat.crc); close_set(el_op); } else if (vdat.set == 'ok') { close_set(el_op); } }}} el = document.getElementById('forms_edit_i'); el1 = document.getElementById('forms_edit_p'); s = sessionStorage.getItem('crc'); if (s != null) {el = '<'+el.name+'='+el.value+';'+sessionStorage.getItem('crc')+'>\r\n';} else {el = '<'+el.name+'='+el.value+';'+el1.value+'>\r\n';} request.open('POST', 'ajax_set_data', true); request.setRequestHeader('Content-Type', 'application/xml'); request.send(el); } function reg_set(el_op, oper) { el = document.getElementById(el_op); if (el != null) { if (oper == 'open') { el.onclick=function() { open_set(this); return false; } } else if (oper == 'close') { el.onclick=function() { close_set(this); return false; } } else if (oper == 'send') { el.onclick=function() { send_set(this); return false; } } } }файл setup.ini
файлы картинок пока не буду выкладывать, если кому будет интересно - напишите
//минимум на 3 одновременных сессии с разных компьютеров)
но стр.24
// не более 5 клиентов
однако стр.25
#define MAX_SRV_CLIENTS 4
Так таки сколько выдержало тестирование?
А в остальном - детский сад.
Коннект с роутером живет типа вечно. А я давал ссылку почитать о проблеме. Соединение WiFi иногда просто разрывается, ну роутер ребутнули к примеру. Что будет?
012передает в UART порт статус в формате013<status=OK>, или <status=E1>Не верю, в стр.59,73 и т.д. совсем другое написано. Определись таки зачем uart: для связи с второй платой или вывода отладки.
701unsignedlongtime_pause(unsignedlongtlo, unsignedlongtln) {702if(tlo <= tln) {703return(tln - tlo);704}else{705return(4294967295 - tln + tlo);706}707}Миллис переполняется? Как мило)))
Про концепцию двух взаимно ребутящихся устройств. Увы, тоже известные грабли. Ребуты могут приходить в любой момент в т.ч. во время записи в энергонезависимую память. При этом инфа бьется и все валится. Надо софт без глюков писать, а не взаимными ребутами развлекатся.
1. в библиотеке есть ограничение на 5 конектов, его можно расширить, но лично мне это не очень нужно. MAX_SRV_CLIENTS=4 я сделал для того, что бы зарезервировать пятый конект на очистку входящего буфера, хотя наверно можно и не делать, но я сделал. Кроме того я тестировал именно множественное подключение
генерил 20 подключений с разных портов, при этом работало стабильно 4 браузера но за счет локальных кешей, как только запускал большие скачки (например страница в параллель грузит 3 файла JS+CSS+HTML) то библиотека сама создает на каждую скачку отдельный клиентский сеанс (клиент один а сеансов я видел до 3х шт) вот в таком режиме 2 браузера работали нормально, третий уже мог выдавать ошибки.
то есть при работе с клиентским кешем (когда изначально прогружают в разное время) хорошо работает на 4х разных машинах, если без кеша - только на 2х
2. >>>Коннект с роутером живет типа вечно.
не вечно, он рубится но потом сам востанавливается (роутером), про ребут роутера - спасибо за замечание, надо будет учесть. Конечно я не испытывал на "дальняк", максимум это на ночь оставлял с записью статистики на клиенте.
3. С UART - да пока не доделал, вернее пока он только на прием работает
4. >>>Миллис переполняется?
мне уже писали много раз, что это лишнее, но я сторонник старой школы, и обработку переполнения отработаю лучше явным образом
5. про ребут в момент записи ERROM - спасибо, возьму на заметку
6. про детский сад, в целом согласен - но у меня пока задача а-ля "тестовый велосипед"
7. просьба прокомментировать подсчет "загружености" (переменная "res_time_loop_sum"), сделал "по наитию", без какой либо теоретической базы.
ps
Вы не поверите чего иногда можно найти в промышленных устройствах на nix :) , в том году ковырял NAS от компании ZUXEL там столько рудиментов от других устройств, что вообще странно как он работает ....
4. Мое дело предупредить. На форуме тут жестко пинают за такое и не зря. Поищи темы, почитай. Там все сто раз разжевано. Еще раз повторюсь, больше читай форум - меньше граблей будет. И про ESP это тоже в полной мере, сервера на нем, ну кто во что гаразд, уже много лет лабают. И много шишек набито. Зачем повторятся.
7. Мысль верная, контролировать надо. Но 5мсек - это целая вечность для МК на 80МГц. Хотя именно веб-сервер в плане быстроты отклика и не критично. Там, где критично, там иногда даже гистограммы собираю. Прямо в "обертке" для миллис.
word Timer; #define SIZE_HISTOGRAMM_LOOP 16 word HistogrammaLoopTime[SIZE_HISTOGRAMM_LOOP]; //последний элемент - максимальная длительность /* Формируем новое системное время для текущего цикла, собираем статистику загрузки, определяем короткий цикл для запуска */ boolean GetNewTime(word* Timer) { word T=millis(); word i=T-*Timer; { if(i>SIZE_HISTOGRAMM_LOOP-2) i=SIZE_HISTOGRAMM_LOOP-2; HistogrammaLoopTime[i]++; if(i>HistogrammaLoopTime[SIZE_HISTOGRAMM_LOOP-1]) HistogrammaLoopTime[SIZE_HISTOGRAMM_LOOP-1]=i; if(!HistogrammaLoopTime[i]) { memset(HistogrammaLoopTime, 0, SIZE_HISTOGRAMM_LOOP*2); } } *Timer=T; return i==0; } void loop(void) { byte d; /* Получаем новое время и собираем статистику о длительностях цикла */ if(GetNewTime(&Timer)) { //при пустых циклах меряем обороты word tmp=OborotProcess();Если просто отдавать страницы - думаю это лишнее. Если вдруг через веб управлять и быстродействие требуется, тогда уже может потребоваться. Для такого WebSocket хорош.