#include "web_server.h" #include "device_list.h" #include "ping.h" #include "esp_http_server.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include #include #include #include #define PING_FORM_MAX_LENGTH (DEVICE_LIST_MAX_SIZE * (4 + ETH_HWADDR_LEN * 2 + 1) - 1) static const char *TAG = "web_server"; static esp_err_t main_page_handler(httpd_req_t *req); static esp_err_t ping_handler(httpd_req_t *req); static esp_err_t redirect_to_main(httpd_req_t *req); static void format_last_seen(TickType_t last_seen, char *text, size_t size); static esp_err_t send_page_header(httpd_req_t *req); static esp_err_t send_device_table(httpd_req_t *req); static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device); static esp_err_t send_page_footer(httpd_req_t *req); static httpd_handle_t start_webserver(void); static void register_http_handlers(httpd_handle_t server); static const httpd_uri_t main_uri = {.uri = "/", .method = HTTP_GET, .handler = main_page_handler, .user_ctx = NULL}; static const httpd_uri_t main_post_uri = {.uri = "/", .method = HTTP_POST, .handler = ping_handler, .user_ctx = NULL}; void web_server_init(void) { httpd_handle_t server_handle = start_webserver(); if (server_handle != NULL) { register_http_handlers(server_handle); } } static httpd_handle_t start_webserver(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); httpd_handle_t server = NULL; if (httpd_start(&server, &config) == ESP_OK) { ESP_LOGI(TAG, "HTTP server started"); } else { ESP_LOGE(TAG, "Failed to start http server"); } return server; } static void register_http_handlers(httpd_handle_t server) { esp_err_t err = httpd_register_uri_handler(server, &main_uri); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register main URI"); } err = httpd_register_uri_handler(server, &main_post_uri); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register main POST URI"); } } static esp_err_t main_page_handler(httpd_req_t *req) { esp_err_t err = httpd_resp_set_type(req, "text/html; charset=utf-8"); if (err != ESP_OK) return err; err = httpd_resp_set_hdr(req, "Cache-Control", "no-store"); if (err != ESP_OK) return err; err = send_page_header(req); if (err != ESP_OK) { ESP_LOGE(TAG, "Page header isn't created"); return err; } err = send_device_table(req); if (err != ESP_OK) { ESP_LOGE(TAG, "Table isn't created"); return err; } err = send_page_footer(req); if (err != ESP_OK) { ESP_LOGE(TAG, "Page footer isn't created"); return err; } return httpd_resp_sendstr_chunk(req, NULL); } static esp_err_t send_page_header(httpd_req_t *req) { return httpd_resp_sendstr_chunk( req, "" "" "" "" "" "ARP Monitoring" "" "" "
" "

ARP Monitoring

" "

Отслеживание устройств в Вашей локальной сети с помощью ARP. Отслеживайте и диагностируйте подключенные устройства в реальном времени.

" ); } static esp_err_t send_device_table(httpd_req_t *req) { esp_err_t err; const size_t count = device_list_count(); size_t online = 0, total = 0; for (size_t index = 0; index < count; index++) { const device_info_t *device = device_list_get_by_index(index); if (device != NULL) { total++; if (device->active) online++; } } static const char *const summary_parts[] = { "
" "
" "

Всего устройств

", "Обнаружено по ARP
" "
" "

Online

", "Сейчас онлайн
" "
" "

Offline

", "Не ответили
" "

Устройства (", ")

ARP / IPv4
" }; const size_t values[] = {total, online, total - online, total}; for (size_t index = 0; index < sizeof(values) / sizeof(values[0]); index++) { err = httpd_resp_sendstr_chunk(req, summary_parts[index]); if (err != ESP_OK) return err; char number[32]; int length = snprintf(number, sizeof(number), "%zu", values[index]); if (length < 0 || (size_t)length >= sizeof(number)) return ESP_FAIL; err = httpd_resp_sendstr_chunk(req, number); if (err != ESP_OK) return err; } err = httpd_resp_sendstr_chunk(req, summary_parts[4]); if (err != ESP_OK) return err; err = httpd_resp_sendstr_chunk( req, "
" "
" "" "" "" "" "" "" "" ); if (err != ESP_OK) return err; if (total == 0) { err = httpd_resp_sendstr_chunk(req, ""); if (err != ESP_OK) return err; } for (size_t index = 0; index < count; index++) { const device_info_t *current_device; current_device = device_list_get_by_index(index); if (current_device == NULL) continue; err = send_device_row(req, current_device); if (err != ESP_OK) return err; } return httpd_resp_sendstr_chunk( req, "
ВыборIP-адресMAC-адресПоследний ARPСтатус
Устройства пока не обнаружены.
Ожидаем входящие ARP-пакеты.
" "

Выберите устройства для проверки доступности.

" "
" "
" "

Последний ARP — время с последнего ARP-пакета устройства. Значения пересчитываются при загрузке страницы.

" "

Статус обновляется при получении ARP и проверке Ping; постоянная проверка доступности не выполняется.

" ); } static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device) { char html[640]; char last_seen[96]; format_last_seen(device->last_seen, last_seen, sizeof(last_seen)); int length = snprintf( html, sizeof(html), "" "" "" "" "%u.%u.%u.%u" "%02X:%02X:%02X:%02X:%02X:%02X" "%s" "%s" "", device->mac.addr[0], device->mac.addr[1], device->mac.addr[2], device->mac.addr[3], device->mac.addr[4], device->mac.addr[5], ip4_addr1(&device->ip), ip4_addr2(&device->ip), ip4_addr3(&device->ip), ip4_addr4(&device->ip), ip4_addr1(&device->ip), ip4_addr2(&device->ip), ip4_addr3(&device->ip), ip4_addr4(&device->ip), device->mac.addr[0], device->mac.addr[1], device->mac.addr[2], device->mac.addr[3], device->mac.addr[4], device->mac.addr[5], last_seen, device->active ? "online" : "offline", device->active ? "Online" : "Offline" ); if (length < 0 || (size_t)length >= sizeof(html)) return ESP_FAIL; return httpd_resp_sendstr_chunk(req, html); } static void format_last_seen(TickType_t last_seen, char *text, size_t size) { TickType_t elapsed = (TickType_t)(xTaskGetTickCount() - last_seen); uint64_t seconds = (uint64_t)elapsed / configTICK_RATE_HZ; if (seconds == 0) snprintf(text, size, "только что"); else if (seconds < 60) snprintf(text, size, "%" PRIu64 " с назад", seconds); else if (seconds < 3600) snprintf(text, size, "%" PRIu64 " мин %" PRIu64 " с назад", seconds / 60, seconds % 60); else if (seconds < 86400) snprintf(text, size, "%" PRIu64 " ч %" PRIu64 " мин назад", seconds / 3600, (seconds % 3600) / 60); else snprintf(text, size, "%" PRIu64 " д %" PRIu64 " ч назад", seconds / 86400, (seconds % 86400) / 3600); } static esp_err_t send_page_footer(httpd_req_t *req) { return httpd_resp_sendstr_chunk( req, "
" "" ); } static esp_err_t ping_handler(httpd_req_t *req) { if (req->content_len == 0) return redirect_to_main(req); if (req->content_len > PING_FORM_MAX_LENGTH) { httpd_resp_set_status(req, "413 Payload Too Large"); httpd_resp_sendstr(req, "Too many selected devices"); return ESP_FAIL; } char *body = malloc(req->content_len + 1); if (body == NULL) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Not enough memory"); return ESP_FAIL; } size_t received = 0; while (received < req->content_len) { int result = httpd_req_recv(req, body + received, req->content_len - received); if (result <= 0) { free(body); if (result == HTTPD_SOCK_ERR_TIMEOUT) httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT, "Request timed out"); return ESP_FAIL; } received += (size_t)result; } if (memchr(body, '\0', received) != NULL) { free(body); return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid form data"); } body[received] = '\0'; ping_selected_devices(body); free(body); return redirect_to_main(req); } static esp_err_t redirect_to_main(httpd_req_t *req) { esp_err_t err = httpd_resp_set_status(req, "303 See Other"); if (err != ESP_OK) return err; err = httpd_resp_set_hdr(req, "Location", "/"); if (err != ESP_OK) return err; err = httpd_resp_set_hdr(req, "Cache-Control", "no-store"); if (err != ESP_OK) return err; return httpd_resp_sendstr(req, ""); }