Базовый WEB сервер

vde69
Offline
Зарегистрирован: 10.01.2016

Основная идея была очень простая:  реализовать отдельный модуль который будет общаться с основной платой по UART и отрабатывать как WEB сервер, в случае если он зависнет - его сможет перезапустить основная плата, и если зависнет основная плата и можно будет удаленно сделать ребут. 

Теперь функционал к которому я стремлюсь

1. Все функциональные страницы сделаны в виде нормальных файлов и лежат на флеше, то есть внешний вид можно отлаживать на любом стандартном WEB сервере запущенном на локальном компьютере. (исключение является сборка Ajax ответа)

2. поддержка основных сервисных страниц (404 и еще некоторых)

3. многопоточность (минимум на 3 одновременных сессии с разных компьютеров)

4. некая "авторизация", с учетом того, что все-же это только для внутренней сети, то довольно упрощенная.

собственно сам проект выложу в первом посте (по сколько его можно менять и добавлять что-то)

Проект тестировался на ESP-01s

 

vde69
Offline
Зарегистрирован: 10.01.2016

Начну с WEB морды, выглядит пока так

 

файл wifi_modul.ino 
/*
Generic ESP8266 Module

Питание: 3,3 В до 250 мА
Объём Flash-памяти: 1 МБ
Беспроводной интерфейс: Wi-Fi 802.11 b/g/n 2,4 ГГц
Тактовая частота: 80 МГц
список команд http://wiki.amperka.ru/_media/%D0%BF%D1%80%D0%BE%D0%B4%D1%83%D0%BA%D1%82... 

воспринимает с UART порта передачу данных в формате
<set_term=33.1;OK>
передает в UART порт статус в формате
<status=OK>, или <status=E1>

*/


#include <Esp.h>
#include <FS.h>
#include <ESP8266WiFi.h>

#include "params.h"

// не более 5 клиентов
#define MAX_SRV_CLIENTS 4

// на рабочей компиляции закоментировать !
#define ON_DEBUG_ECHO 

#ifdef ON_DEBUG_ECHO
#include <string.h>
char debug_client[MAX_SRV_CLIENTS+1];
#endif

// -----------------------------------------------------------

// Create an instance of the server
// specify the port to listen on as an argument
WiFiServer server(80);

t_wifi_clients clients[MAX_SRV_CLIENTS];
t_params UART_Params;
t_params FILE_Params;

unsigned long res_time_loop_start; 	
unsigned long res_time_loop_sum;
uint8_t res_uart_count = 1;

// это ключ который хранится в клиентской сесии и подтверждает, что она авторизована
// это не очень надежный способ, но вполне достаточный для использования в домашней подсети
// ключ меняется при каждом рестарте ESP
char crc_pass[11];

void setup() {
	Serial.begin(57600);

	while (!SPIFFS.begin()) { 
		delay(100);
		Serial.println(F("ERROR INIT SPIFFS."));
		delay(10000);
	}

	clear_param_tec(&UART_Params);
	clear_param_tec(&FILE_Params);
	// инициализация массивов
	for (uint8_t i = 0; i < PARAM_COUNT; i++) {
		out_value[i][0] = 0;
		out_status[i][0] = 0;
	}

	while (!Read_Param_SD()) {
		delay(100);
		Serial.println(F("ERROR READ setup.ini"));
		delay(10000);
	}

	// параметры speedOfLight, sid, pass считаны из файла setup.ini

	// Connect to WiFi network
	Serial.println();
	Serial.print(F("Connecting to "));
	Serial.println(sid);

	WiFi.mode(WIFI_STA);
	WiFi.begin(&(sid[0]), &(pass[0]));

	while (WiFi.status() != WL_CONNECTED)	{
		delay(5000);
		Serial.print(F("."));
		Serial.print(sid);
		Serial.println(F("."));
	}
	// сгенерируем ключ 
	// генерация после подключения к сети даст достаточно случайное значение
	uint32_t crc_pass2 = 0;
	uint32_t crc_pass1 = (uint32_t)micros();
	crc_pass2 = crc_pass1 << 4;
	crc_pass2 = crc_pass2 ^ crc_pass1 ;
	crc_pass2 = crc_pass2 << (crc_pass2 % 10);
	crc_pass2 = crc_pass2 ^ crc_pass1 ;
	crc_pass2 = crc_pass2 << (crc_pass2 % 10);
	crc_pass2 = crc_pass2 ^ crc_pass1 ;
	
	sprintf(crc_pass,"%d",crc_pass2);

	Serial.println();
	Serial.println(F("WiFi connected"));

	// Start the server
	server.begin();
	Serial.println(F("Server started"));

	// Print the IP address
	Serial.println(WiFi.localIP());

	// инициация счетчика производительности
	res_time_loop_start = millis();
	res_time_loop_sum = 0;

#ifdef ON_DEBUG_ECHO
	for(uint8_t i = 0; i < MAX_SRV_CLIENTS; i++){
		debug_client[i] = '-';
	}
	debug_client[MAX_SRV_CLIENTS] = 0;
#endif

}

void loop() {
	// запомним старт цикла
	unsigned long res_time_loop = millis(); 

	// обработчик UART
	LoopReadUart();

#ifdef ON_DEBUG_ECHO
char old_debug_client[MAX_SRV_CLIENTS];
memcpy(&(old_debug_client[0]), &(debug_client[0]), MAX_SRV_CLIENTS+1);
#endif

	uint8_t i;
	// сначала закроем все просроченные сессии
	for(i = 0; i < MAX_SRV_CLIENTS; i++){
		if ( clients[i].client && !clients[i].client.available()) {
			if (time_pause(clients[i].p.time_loop_new, millis()) > 11000) {
				// держим не более 11 сек
				clients[i].client.stop();
				clear_param(&(clients[i].p));
			}
		}
	}

	// попытка создание новых клиентских сессии
	if (server.hasClient()){
		for(i = 0; i < MAX_SRV_CLIENTS; i++){
			if (!clients[i].client || !clients[i].client.connected()){
				clients[i].client = server.available();
				clients[i].p.time_loop_new = millis();
				continue;
			}
		}
		//нет свободных соеденинений
		WiFiClient serverClient = server.available();
		if (serverClient) {
			while (serverClient.available()) { serverClient.read(); }
			out_503(&serverClient);
			serverClient.stop();
		}
	}

	// для каждого активного клиента запускаем обработчик цикла
	for(i = 0; i < MAX_SRV_CLIENTS; i++){
		if ( clients[i].client ) {
			LoopReadWiFi(&(clients[i]));
		}
	}

	unsigned long t_loop = time_pause(res_time_loop, millis());
	if (t_loop >= 5) { // циклы менее 0.005 сек считаем "холостыми"
		res_time_loop_sum = res_time_loop_sum + t_loop;
	}

	if (time_pause(res_time_loop_start,millis()) > 1000) {
		if (res_uart_count < 10 ) { res_uart_count++ ;}
		call_res();
		res_time_loop_start = millis();
	}


#ifdef ON_DEBUG_ECHO
for(i = 0; i < MAX_SRV_CLIENTS; i++){
	if ( clients[i].client ) { debug_client[i] = 'O'; }
	else { debug_client[i] = '-'; }
}
if (strcmp(&debug_client[0],&old_debug_client[0]) != 0) {
Serial.print("Sesion: ");
Serial.print(debug_client);
Serial.print("; Free RAM="); 
Serial.print(ESP.getFreeHeap());
Serial.println();
}
#endif


}

// -----------------------------------------------------------
// процедуры формирования страниц вывода

void out_page_file(WiFiClient *client, char *FileName, char *Content)
{
	File f = SPIFFS.open(FileName, "r");
	if (!f)
	{
		out_404(client);
	}
	else
	{
		client->print(F(
			"HTTP/1.1 200 OK\r\n"
			"Cache-Control: max-age=31536000\r\n"
			"Content-Type: "));
		client->print(Content);
		out_br(client);
		client->print(F("Content-Length: "));
		client->print(f.size(), DEC);
		out_br(client);
		out_br(client);
		while (f.available())
		{
			client->write(f.read());
		}
		out_br(client);
		f.close();
	}
}

void out_ajax_ok(WiFiClient *client, uint8_t autorization){
	//
	// формат отправки: {"set":"ok"}
	//

	//	0 - ошибка или пусто
	// 	1 - получен пароль, нужно отправить подтверждение и идентификатор
	//	2 - получен идентификатор, отправляем только идентификатор
	String str_l = "{\"set\":\"error\"}";
	if (autorization == 1) { str_l = "{\"set\":\"ok\",\"crc\":\"" + String(crc_pass) + ("\"}"); } 
	else if (autorization == 2) { str_l = "{\"set\":\"ok\"}"; } 

	client->print(F(
		"HTTP/1.1 200 OK\r\n"
		"Content-Type: application/json\r\n"));
	client->print(F("Content-Length: "));
	client->print(str_l.length(), DEC);
	out_br(client);
	out_br(client);
	client->print(str_l);
	out_br(client);
}

void out_ajax_data(WiFiClient *client){
	//
	// формат отправки: {"t_home_1":{"value":{"data-v":"030"},"status":{"data-st":"OK"}}}
	// в этом примере меняются только: t_home_1,030,OK
	// можно менять/добавлять и value, status, data-v, data-st но тогда надо менять HTML и CSS файлы
	//


	String str = "{";
	for (uint8_t i = 0; i < PARAM_COUNT; i++) {
		if ((out_id[i].flag & 2) > 0) {
			str = str + (String)("\"") + (String)(out_id[i].id) + (String)("\":{\"value\":{\"data-v\":\"") + (String)(out_value[i]) + (String)("\"},\"status\":{\"data-st\":\"") + (String)(out_status[i]) + (String)("\"}},");
		}
	}
	str[str.length() - 1] = '}';

	client->print(F(
		"HTTP/1.1 200 OK\r\n"
		"Content-Type: application/json\r\n"));
	client->print(F("Content-Length: "));
	client->print(str.length(), DEC);
	out_br(client);
	out_br(client);
	client->print(str);
	out_br(client);
}

void out_favicon(WiFiClient *client) {
	client->print(F(
		"HTTP/1.1 200 OK\r\n"
		"Cache-Control: max-age=31536000\r\n"
		"Transfer-Encoding: chunked\r\n"
		"Content-Type: image/x-icon\r\n"));
	out_br(client);
	out_br(client);
}

void out_404(WiFiClient *client) {
	client->print(F(
		"HTTP/1.1 200 OK\r\n"
		"Transfer-Encoding: chunked\r\n"
		"\r\n"
		"<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"<head>\r\n"
		"<title>404</title>\r\n"
		"</head>\r\n"
		"<body>\r\n"
		"<h1>ERROR 404</h1>\r\n"
		"<div>\r\n"
		"</div>\r\n"
		"</body>\r\n"
		"</html>\r\n"));
	out_br(client);
}

void out_503(WiFiClient *client) {

	client->print(F(
		"HTTP/1.1 503 OK\r\n"
		"Transfer-Encoding: chunked\r\n"
		"\r\n"
		"<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"<head>\r\n"
		"<title>503</title>\r\n"
		"</head>\r\n"
		"<body>\r\n"
		"<h1>Server is busy (ERROR 503)</h1>\r\n"
		"<div>\r\n"
		"</div>\r\n"
		"</body>\r\n"
		"</html>\r\n"));
	out_br(client);
}

// -----------------------------------------------------------
// процедуры логики главного цикла

void LoopReadWiFi(t_wifi_clients* str_client) {
	uint8_t c;

	unsigned long time_loop = millis();
	// набираем буфер, и ждем конца строки
	// при этом ловим  состояние заголоывок или данные
	while (str_client->client.available())	{
		str_client->p.time_loop_new = millis(); // обновим данные о времени сесии

		// если читаем данные более 0.1 сек - надо прерватся и поделится ресурсами с другими задачами
		if (time_pause(time_loop, millis()) > 100) { break; }
		c = str_client->client.read(); 

		if (c > 31) {
			// символы с кодом > 127 пропускаем
			if (c > 127) { continue; }

			// если нам пришла строка больше размера нашего буфера мы ее усекаем 
			// будем читать буфер и пока не найдем символ конец строки
			// или пока не пройдет таймаут
			if (str_client->p.tec_size >= BUFER_COUNT) { continue; }

			// если сюда попали, значит символ нужный
			// запишем в буфер значение и увеличим переменную считанных данных
			// и зафиксируем время для расчета таймаута
			*((uint8_t *)(str_client->p.buf + str_client->p.tec_size)) = c;
			str_client->p.tec_size++;
			str_client->p.Request_time_loop = millis();
			str_client->p.Request_EOF = 0;
		} else if (c == 0x0D) {
			str_client->p.Request_EOF = 3328;
		} else if (c == 0x0A) {
			str_client->p.Request_EOF = str_client->p.Request_EOF + 10;
		} else {
			str_client->p.Request_EOF = 0;
		}

		// если получен конец строки то надо прервать цикл чтения данных
		if (str_client->p.Request_EOF == 3338) { break; }
	}

	// если не было данных более 0.1 сек, считаем, что получено завершение запроса
	if 	(time_pause(str_client->p.Request_time_loop, millis()) > 100) {
		str_client->p.Request_EOF == 4000; // ставим больше чем 3338
	}

	if (str_client->p.Request_EOF >= 3338) {

		// если сюда попали, значит строка закончена
		if (str_client->p.tec_size > 0) {
			if (str_client->p.Request_Flag == 0) { parse_buf_wifi_caption(str_client); } // это завершение строки заголовка
			if (str_client->p.Request_Flag == 1) { parse_buf_wifi_data(str_client); }  // это завершение строки данных
			if (str_client->p.Request_EOF == 4000) { str_client->p.Request_Flag = 2; } // если это по таймауту - надо переходить к ответу
		} else { str_client->p.Request_Flag++; }

		if ( str_client->p.Request_Flag >= 2 ) {
			// если мы получили 3 параметра - значит что-то надо делать
			if ((str_client->p.Request.length() > 0) &&
				(str_client->p.Request_Type.length() > 0) &&
				(str_client->p.Request_Host.length() > 0)) {

				// формируем ответ
				if (str_client->p.Request == "/")	{

					out_page_file(&(str_client->client), "/index.html", "text/html");

				} else if (str_client->p.Request == "/setup.ini")	{

					out_404(&(str_client->client)); // запрет на скачку этого файла, в нем настройки сети

				} else if (str_client->p.Request == "/ajax_data")	{

					out_ajax_data(&(str_client->client)); 

				} else if (str_client->p.Request == "/ajax_set_data")	{

					out_ajax_ok(&(str_client->client), str_client->p.autorization); 

				} else if (str_client->p.Request == "/favicon.ico") {

					out_favicon(&(str_client->client)); // уменьшаем запросы на выдачу этого файла
										// если положить иконку можно будет и показывать
				} else {

					int16_t inum = str_client->p.Request.indexOf('.');
					if (inum >= 0) {
						String file_ext = str_client->p.Request.substring(inum + 1);
						// все остальные файлы по расширениям
						if (file_ext == "css") {

							out_page_file(&(str_client->client), (char *)(str_client->p.Request.c_str()), "text/css");

						} else if (file_ext == "png") {

							out_page_file(&(str_client->client), (char *)(str_client->p.Request.c_str()), "image/png");

						} else if (file_ext == "jpg") {

							out_page_file(&(str_client->client), (char *)(str_client->p.Request.c_str()), "image/jpg");

						} else if (file_ext == "html") {

							out_page_file(&(str_client->client), (char *)(str_client->p.Request.c_str()), "text/html");

						} else if (file_ext == "js") {

							out_page_file(&(str_client->client), (char *)(str_client->p.Request.c_str()), "application/javascript");
							
						}						
					} else {
						out_404(&(str_client->client));
					}
				}
			}
			// здесь в любом случае код зачистки и ждем нового пакета
			clear_param(&(str_client->p));
		}
	}
}

void LoopReadUart() {
	//
	// ожидаемый формат: <id=value;status>
	//
	uint8_t c;
	unsigned long time_loop = millis();
	while (Serial.available())
	{
		if (time_pause(time_loop, millis()) > 100) {
			// если читаем данные более 0.1 сек - надо прерватся и поделится ресурсами с другими задачами
			break;
		}

		c = Serial.read(); 

		if (c < 32) { // эти символы пропускаем и считаем сначала
			read_0_31(&UART_Params);
		}
		else if (UART_Params.step == 0)	{ // ждем начала блока "<"
			read_param_step_0(&UART_Params, c); 
		}
		else if (UART_Params.step == 1) { // читаем и ищем идентификатор, до символа "="
			read_param_step_1(&UART_Params, c);
		}
		else if (UART_Params.step == 2) { // читаем VALUE, до символа ";"
			read_param_step_2(&UART_Params, c);
		}
		else if (UART_Params.step == 3) { // читаем STATUS, до символа ">"
			read_param_step_3(&UART_Params, c);
		}

		if (UART_Params.step == 4)	{ // копируем текущие параметры на постоянное место
			read_param_step_4(&UART_Params);
			if (res_uart_count > 1 ) { res_uart_count-- ;}
		}
	}
}

boolean Read_Param_SD() {
	//
	// ожидаемый формат строк: <id=value;value2>
	//

	// читаем файл инициализации setup.ini, и устанавливаем параметры
	uint8_t c;
	File f = SPIFFS.open("/setup.ini", "r");
	if (!f)	{
		Serial.println(F("ERROR READ setup.ini"));
		return false;
	} else {
		while (f.available()) {
			c = f.read();

			if (c < 32) { // эти символы пропускаем и считаем сначала
				read_0_31(&FILE_Params);
			}
			else if (FILE_Params.step == 0)	{ // ждем начала блока "<"
				read_param_step_0(&FILE_Params, c); 
			}
			else if (FILE_Params.step == 1) { // читаем и ищем идентификатор, до символа "="
				read_param_step_1(&FILE_Params, c);
			}
			else if (FILE_Params.step == 2) { // читаем VALUE, до символа ";"
				read_param_step_2(&FILE_Params, c);
			}
			else if (FILE_Params.step == 3) { // читаем STATUS, до символа ">"
				read_param_step_3(&FILE_Params, c);
			}

			if (FILE_Params.step == 4)	{ // копируем текущие параметры на постоянное место
				read_param_step_4(&FILE_Params);
			}
		}
		f.close(); //Close file
	}

	return true;
}

void call_res() {
	// ----- res_mem ------
	uint32_t par = ESP.getFreeHeap();
	if (par <= 10000) {
		par = 100;
	} else if (par >= 60000) {
		par = 0;
	} else {
		par = (60000 - par) * 2 / 1000;
		par = par - (par % 10); // это округление последнего знака, то есть 10%, 20%, 30% и т.д. нужно для css
	}
	sprintf(res_mem,"%d",par);

	// ----- res_cpu ------
	unsigned long t_delta = time_pause(res_time_loop_start, millis());
	if ( t_delta > 1000) {
		par = 0;
		if (res_time_loop_sum > 0) {
			par = (uint32_t)(t_delta / res_time_loop_sum / 10);
		}
		par = par - (par % 10); // это округление последнего знака, то есть 10%, 20%, 30% и т.д. нужно для css
		sprintf(res_cpu,"%d",par);
		res_time_loop_start = millis();
		res_time_loop_sum = 0;
	}

	// ----- res_uart ------
	par = res_uart_count * 10;
	sprintf(res_uart,"%d",par);


	// ----- res_wifi ------
	par = 0;
	for(uint8_t i = 0; i < MAX_SRV_CLIENTS; i++){ 
		if ( clients[i].client ) { par++; }
	}
	par = par * 100 / MAX_SRV_CLIENTS;
	par = par - (par % 10); // это округление последнего знака, то есть 10%, 20%, 30% и т.д. нужно для css
	sprintf(res_wifi,"%d",par);

}

// -----------------------------------------------------------
// вспомогательные процедуры

void clear_param(t_params* par) {
	par->Request = "";
	par->Request_Type = "";
	par->Request_Host = "";
	par->Request_Content_length = 0;
	par->Request_EOF = 0;
	par->Request_Flag = 0;
}

void clear_param_tec(t_params* par) {
	par->value_tec[0] = 0;
	par->status_tec[0] = 0;
}

void parse_buf_wifi_data(t_wifi_clients* str_client) {
	// для начала поставим нулевой символ в конец данных буфера
	*((uint8_t *)(str_client->p.buf + str_client->p.tec_size)) = 0;

	uint8_t c;
	for (uint8_t i = 0; i < BUFER_COUNT; i++)	{
		c = str_client->p.buf[i]; 
		if (c == 0) {
			break;
		} else {
			if (c < 32) { // эти символы пропускаем и считаем сначала
				read_0_31(&(str_client->p));
			}
			else if (str_client->p.step == 0)	{ // ждем начала блока "<"
				read_param_step_0(&(str_client->p), c); 
			}
			else if (str_client->p.step == 1) { // читаем и ищем идентификатор, до символа "="
				read_param_step_1(&(str_client->p), c);
			}
			else if (str_client->p.step == 2) { // читаем VALUE, до символа ";"
				read_param_step_2(&(str_client->p), c);
			}
			else if (str_client->p.step == 3) { // читаем STATUS, до символа ">"
				read_param_step_3(&(str_client->p), c);
			}

			if (str_client->p.step == 4)	{ // копируем текущие параметры на постоянное место

				str_client->p.step = 0;
				memcpy(&(out_value[str_client->p.tec_pointer][0]), &(str_client->p.value_tec[0]), PARAM_VALUE_COUNT+1);

				// в строке str_client->p.status_tec лежит или пароль или "null" или crc_pass 
				if ( strcmp(str_client->p.status_tec, crc_pass) == 0 ) {
					// нам прислали правильный ключ, обратно ничего не отправляем
					str_client->p.autorization = 2;
				} else if ( strcmp (str_client->p.status_tec, admin) == 0 ) {
					// нам прислали пароль, в ответ отправим crc_pass
					str_client->p.autorization = 1;
				} else {
					// нам прислали лажу
					str_client->p.autorization = 0;
				}

#ifdef ON_DEBUG_ECHO
Serial.print("Value: ");
Serial.print(out_value[str_client->p.tec_pointer]);
Serial.print("; admin="); 
Serial.print(admin);
Serial.print("; pass="); 
Serial.print(str_client->p.status_tec);
Serial.print("; crc="); 
Serial.print(crc_pass);
Serial.print("; aut="); 
Serial.print(str_client->p.autorization);
Serial.println();
#endif


			}
		}
	}

	str_client->p.tec_size = 0;
}

void parse_buf_wifi_caption(t_wifi_clients* str_client) {
	str_client->p.autorization = 0;

	String inString = "";
	String file_ext = "";

	// для начала поставим нулевой символ в конец данных буфера
	*((uint8_t *)(str_client->p.buf + str_client->p.tec_size)) = 0;

	// это элементы заголовка, на них у меня нет быстрого парсера, идем через строки
	inString = str_client->p.buf;

	if (inString.startsWith("Host: ")) {
		str_client->p.Request_Host = inString.substring(6);
	} else if (inString.startsWith("GET "))	{
		str_client->p.Request = inString.substring(4);
		str_client->p.Request_Host = "";
		str_client->p.Request_Type = "GET";
		if (str_client->p.Request.indexOf(' ') != -1)	{
			str_client->p.Request.remove(str_client->p.Request.indexOf(' '));
		}
	} else if (inString.startsWith("POST ")) {
		str_client->p.Request = inString.substring(5);
		str_client->p.Request_Host = "";
		str_client->p.Request_Type = "POST";
		if (str_client->p.Request.indexOf(' ') != -1)	{
			str_client->p.Request.remove(str_client->p.Request.indexOf(' '));
		}
	} else if (inString.startsWith("Content-Length: ")) {
		str_client->p.Request_Content_length = atoi(inString.substring(16).c_str());
	}
	str_client->p.tec_size = 0;
}

void out_br(WiFiClient *client) {
	client->print(F("\r\n"));
}

unsigned long time_pause(unsigned long tlo, unsigned long tln) {
	if (tlo <= tln)	{
		return (tln - tlo);
	} else {
		return (4294967295 - tln + tlo);
	}
}

 

файл 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

<sid=home>
<pass=123>
<serial=57600>
<admin=123>

файлы картинок пока не буду выкладывать, если кому будет интересно - напишите

 

Logik
Offline
Зарегистрирован: 05.08.2014

//минимум на 3 одновременных сессии с разных компьютеров)

но стр.24 

// не более 5 клиентов

однако стр.25

#define MAX_SRV_CLIENTS 4

Так таки сколько выдержало тестирование?

 

А в остальном  - детский сад. 

Коннект с роутером живет типа вечно. А я давал ссылку почитать о проблеме. Соединение WiFi иногда просто разрывается, ну роутер ребутнули к примеру. Что будет?

012 передает в UART порт статус в формате
013 <status=OK>, или <status=E1>

 Не верю, в стр.59,73 и т.д. совсем другое написано. Определись таки зачем uart: для связи с второй платой или вывода отладки.

701 unsigned long time_pause(unsigned long tlo, unsigned long tln) {
702     if (tlo <= tln)  {
703         return (tln - tlo);
704     else {
705         return (4294967295 - tln + tlo);
706     }
707 }

Миллис переполняется? Как мило)))

Про концепцию двух взаимно ребутящихся устройств. Увы, тоже известные грабли. Ребуты могут приходить в любой момент в т.ч. во время записи в энергонезависимую память.  При этом инфа бьется и все валится. Надо софт без глюков писать, а не взаимными ребутами развлекатся. 

 
vde69
Offline
Зарегистрирован: 10.01.2016

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 там столько рудиментов от других устройств, что вообще странно как он работает .... 

 

Logik
Offline
Зарегистрирован: 05.08.2014

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 хорош.