#include "ui/apps/travel/web/web_handlers.h" #include "esp_log.h" #include "cJSON.h" #include #include #include static const char* TAG = "TravelWebHandler"; namespace travel { WebHandler::WebHandler( SettingHandler* setting_handler, NetworkHandler* network_handler ) : web_server_(std::make_unique()) , setting_handler_(setting_handler) , network_handler_(network_handler) , mtr_handler_(std::make_unique()) , auth_key_(generate_auth_key_()) { } WebHandler::~WebHandler() { stop_web_server(); } std::string WebHandler::generate_auth_key_() { // Generate a random 16-character hex key std::stringstream ss; for (int i = 0; i < 8; i++) { ss << std::hex << std::setw(2) << std::setfill('0') << (esp_random() & 0xFF); } return ss.str(); } esp_err_t WebHandler::start_web_server() { uint16_t port = web_server_->start(auth_key_, WEB_SERVER_PORT); if (port == 0) { ESP_LOGE(TAG, "Failed to start web server"); return ESP_FAIL; } esp_err_t err = register_web_endpoints_(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register endpoints: %s", esp_err_to_name(err)); web_server_->stop(); return err; } ESP_LOGI(TAG, "Web server started on port %d", port); return ESP_OK; } esp_err_t WebHandler::stop_web_server() { if (web_server_) { web_server_->stop(); } return ESP_OK; } std::string WebHandler::get_url() const { std::string ip = get_device_ip(); if (ip.empty()) { return ""; } return "http://" + ip + ":" + std::to_string(WEB_SERVER_PORT) + "/?auth=" + auth_key_; } std::string WebHandler::get_device_ip() const { if (!network_handler_) { return ""; } return network_handler_->get_wifi_handler().get_current_ip(); } uint16_t WebHandler::get_port() const { return WEB_SERVER_PORT; } esp_err_t WebHandler::register_web_endpoints_() { // Main settings page httpd_uri_t settings_uri = { .uri = "/", .method = HTTP_GET, .handler = settings_page_handler_, .user_ctx = this }; ESP_ERROR_CHECK(web_server_->register_uri_handler(&settings_uri)); // Get MTR lines httpd_uri_t lines_uri = { .uri = "/api/lines", .method = HTTP_GET, .handler = get_lines_handler_, .user_ctx = this }; ESP_ERROR_CHECK(web_server_->register_uri_handler(&lines_uri)); // Get saved routes httpd_uri_t routes_uri = { .uri = "/api/routes", .method = HTTP_GET, .handler = get_routes_handler_, .user_ctx = this }; ESP_ERROR_CHECK(web_server_->register_uri_handler(&routes_uri)); // Add route httpd_uri_t add_route_uri = { .uri = "/api/routes", .method = HTTP_POST, .handler = add_route_handler_, .user_ctx = this }; ESP_ERROR_CHECK(web_server_->register_uri_handler(&add_route_uri)); // Remove route httpd_uri_t remove_route_uri = { .uri = "/api/routes", .method = HTTP_DELETE, .handler = remove_route_handler_, .user_ctx = this }; ESP_ERROR_CHECK(web_server_->register_uri_handler(&remove_route_uri)); // Save settings (polling interval) httpd_uri_t save_uri = { .uri = "/api/settings", .method = HTTP_POST, .handler = save_settings_handler_, .user_ctx = this }; ESP_ERROR_CHECK(web_server_->register_uri_handler(&save_uri)); return ESP_OK; } esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) { WebHandler* handler = static_cast(req->user_ctx); // Check auth char auth_param[33] = {0}; if (httpd_req_get_url_query_str(req, auth_param, sizeof(auth_param)) == ESP_OK) { char auth_value[33] = {0}; if (httpd_query_key_value(auth_param, "auth", auth_value, sizeof(auth_value)) == ESP_OK) { if (handler->auth_key_ != auth_value) { httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); return ESP_FAIL; } } else { httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Missing auth"); return ESP_FAIL; } } else { httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Missing auth"); return ESP_FAIL; } // HTML page with inline CSS and JavaScript const char* html = R"html( MTR Travel Settings

MTR Travel 設定

新增路線

已儲存路線

更新頻率

30
)html"; httpd_resp_set_type(req, "text/html"); httpd_resp_send(req, html, strlen(html)); return ESP_OK; } esp_err_t WebHandler::get_lines_handler_(httpd_req_t* req) { WebHandler* handler = static_cast(req->user_ctx); // Get all lines from MTR handler std::vector lines = handler->mtr_handler_->get_lines(); cJSON* root = cJSON_CreateArray(); for (const auto& line : lines) { cJSON* line_obj = cJSON_CreateObject(); cJSON_AddStringToObject(line_obj, "code", line.code()); cJSON_AddStringToObject(line_obj, "name", line.name()); cJSON_AddStringToObject(line_obj, "color", line.color()); // Add stations cJSON* stations_arr = cJSON_CreateArray(); const auto* stations = line.stations(); if (stations) { for (const auto& station : *stations) { cJSON* station_obj = cJSON_CreateObject(); cJSON_AddStringToObject(station_obj, "code", station.code()); cJSON_AddStringToObject(station_obj, "name", station.name()); cJSON_AddItemToArray(stations_arr, station_obj); } } cJSON_AddItemToObject(line_obj, "stations", stations_arr); cJSON_AddItemToArray(root, line_obj); } char* json_str = cJSON_PrintUnformatted(root); cJSON_Delete(root); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, json_str ? json_str : "[]", json_str ? strlen(json_str) : 2); if (json_str) { free(json_str); } return ESP_OK; } esp_err_t WebHandler::get_routes_handler_(httpd_req_t* req) { WebHandler* handler = static_cast(req->user_ctx); cJSON* root = cJSON_CreateObject(); // Add routes cJSON* routes_arr = cJSON_CreateArray(); const auto& routes = handler->setting_handler_->get_routes(); for (const auto& route : routes) { cJSON* route_obj = cJSON_CreateObject(); cJSON_AddStringToObject(route_obj, "line_code", route.line_code.c_str()); cJSON_AddStringToObject(route_obj, "line_name", route.line_name.c_str()); cJSON_AddStringToObject(route_obj, "line_color", route.line_color.c_str()); cJSON_AddStringToObject(route_obj, "station_code", route.station_code.c_str()); cJSON_AddStringToObject(route_obj, "station_name", route.station_name.c_str()); cJSON_AddStringToObject(route_obj, "direction", route.direction.c_str()); cJSON_AddStringToObject(route_obj, "direction_name", route.direction_name.c_str()); cJSON_AddItemToArray(routes_arr, route_obj); } cJSON_AddItemToObject(root, "routes", routes_arr); // Add polling interval cJSON_AddNumberToObject(root, "polling_interval", handler->setting_handler_->get_polling_interval()); char* json_str = cJSON_PrintUnformatted(root); cJSON_Delete(root); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, json_str ? json_str : "{}", json_str ? strlen(json_str) : 2); if (json_str) { free(json_str); } return ESP_OK; } esp_err_t WebHandler::add_route_handler_(httpd_req_t* req) { WebHandler* handler = static_cast(req->user_ctx); // Read request body char buf[512]; int received = 0; int remaining = req->content_len; std::string body; while (remaining > 0) { received = httpd_req_recv(req, buf, std::min(remaining, (int)sizeof(buf) - 1)); if (received <= 0) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to read request"); return ESP_FAIL; } buf[received] = '\0'; body += buf; remaining -= received; } // Parse JSON cJSON* root = cJSON_Parse(body.c_str()); if (!root) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); return ESP_FAIL; } RoutePair route; cJSON* item; item = cJSON_GetObjectItem(root, "line_code"); if (item && cJSON_IsString(item)) route.line_code = item->valuestring; item = cJSON_GetObjectItem(root, "line_name"); if (item && cJSON_IsString(item)) route.line_name = item->valuestring; item = cJSON_GetObjectItem(root, "line_color"); if (item && cJSON_IsString(item)) route.line_color = item->valuestring; item = cJSON_GetObjectItem(root, "station_code"); if (item && cJSON_IsString(item)) route.station_code = item->valuestring; item = cJSON_GetObjectItem(root, "station_name"); if (item && cJSON_IsString(item)) route.station_name = item->valuestring; item = cJSON_GetObjectItem(root, "direction"); if (item && cJSON_IsString(item)) route.direction = item->valuestring; item = cJSON_GetObjectItem(root, "direction_name"); if (item && cJSON_IsString(item)) route.direction_name = item->valuestring; cJSON_Delete(root); if (route.line_code.empty() || route.station_code.empty() || route.direction.empty()) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing required fields"); return ESP_FAIL; } // Add route handler->setting_handler_->add_route(route); handler->setting_handler_->save_settings(); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, "{\"success\":true}", 16); return ESP_OK; } esp_err_t WebHandler::remove_route_handler_(httpd_req_t* req) { WebHandler* handler = static_cast(req->user_ctx); // Read request body char buf[128]; int received = 0; int remaining = req->content_len; std::string body; while (remaining > 0) { received = httpd_req_recv(req, buf, std::min(remaining, (int)sizeof(buf) - 1)); if (received <= 0) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to read request"); return ESP_FAIL; } buf[received] = '\0'; body += buf; remaining -= received; } // Parse JSON cJSON* root = cJSON_Parse(body.c_str()); if (!root) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); return ESP_FAIL; } cJSON* index_item = cJSON_GetObjectItem(root, "index"); if (!index_item || !cJSON_IsNumber(index_item)) { cJSON_Delete(root); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing index"); return ESP_FAIL; } int index = index_item->valueint; cJSON_Delete(root); handler->setting_handler_->remove_route(index); handler->setting_handler_->save_settings(); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, "{\"success\":true}", 16); return ESP_OK; } esp_err_t WebHandler::save_settings_handler_(httpd_req_t* req) { WebHandler* handler = static_cast(req->user_ctx); // Read request body char buf[256]; int received = 0; int remaining = req->content_len; std::string body; while (remaining > 0) { received = httpd_req_recv(req, buf, std::min(remaining, (int)sizeof(buf) - 1)); if (received <= 0) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to read request"); return ESP_FAIL; } buf[received] = '\0'; body += buf; remaining -= received; } // Parse JSON cJSON* root = cJSON_Parse(body.c_str()); if (!root) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); return ESP_FAIL; } cJSON* interval_item = cJSON_GetObjectItem(root, "polling_interval"); if (interval_item && cJSON_IsNumber(interval_item)) { uint32_t interval = interval_item->valueint; handler->setting_handler_->set_polling_interval(interval); } cJSON_Delete(root); handler->setting_handler_->save_settings(); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, "{\"success\":true}", 16); return ESP_OK; } } // namespace travel