- Added MainUIHandler class to manage the main UI and polling for arrival data. - Introduced SettingsUI class for displaying QR code and configuration options. - Created SettingsUIHandler to manage settings UI lifecycle and web server interactions. - Developed WebHandler to handle HTTP requests for MTR route settings, including adding and removing routes. - Implemented web endpoints for fetching MTR lines, routes, and saving settings. - Enhanced UI with responsive design for e-ink displays and added error handling for web interactions.
719 lines
20 KiB
C++
719 lines
20 KiB
C++
#include "ui/apps/travel/web/web_handlers.h"
|
|
#include "esp_log.h"
|
|
#include "cJSON.h"
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
static const char* TAG = "TravelWebHandler";
|
|
|
|
namespace travel {
|
|
|
|
WebHandler::WebHandler(
|
|
SettingHandler* setting_handler,
|
|
NetworkHandler* network_handler
|
|
)
|
|
: web_server_(std::make_unique<WebServerHandler>())
|
|
, setting_handler_(setting_handler)
|
|
, network_handler_(network_handler)
|
|
, mtr_handler_(std::make_unique<MTRNextTrainHandler>())
|
|
, 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<WebHandler*>(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(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MTR Travel Settings</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: #f5f5f5;
|
|
padding: 20px;
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
}
|
|
h1 { font-size: 24px; margin-bottom: 20px; color: #333; }
|
|
h2 { font-size: 18px; margin: 20px 0 10px; color: #555; }
|
|
.card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin-bottom: 15px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.form-group { margin-bottom: 15px; }
|
|
label { display: block; margin-bottom: 5px; font-weight: 500; color: #555; }
|
|
select, input[type="number"] {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 16px;
|
|
}
|
|
button {
|
|
background: #007DC5;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 20px;
|
|
border-radius: 4px;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
}
|
|
button:hover { background: #005a8c; }
|
|
button.secondary {
|
|
background: #6c757d;
|
|
}
|
|
button.danger {
|
|
background: #dc3545;
|
|
}
|
|
.route-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 10px;
|
|
background: #f8f9fa;
|
|
border-radius: 4px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.route-info { flex: 1; }
|
|
.route-line {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
border-radius: 3px;
|
|
color: white;
|
|
font-size: 12px;
|
|
margin-right: 5px;
|
|
}
|
|
.status { margin-top: 10px; padding: 10px; border-radius: 4px; }
|
|
.status.success { background: #d4edda; color: #155724; }
|
|
.status.error { background: #f8d7da; color: #721c24; }
|
|
.slider-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
input[type="range"] { flex: 1; }
|
|
.value-display { min-width: 60px; text-align: right; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>MTR Travel 設定</h1>
|
|
|
|
<div class="card">
|
|
<h2>新增路線</h2>
|
|
<div class="form-group">
|
|
<label>路線</label>
|
|
<select id="line_select" onchange="updateStations()">
|
|
<option value="">請選擇路線</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>出發站</label>
|
|
<select id="station_select">
|
|
<option value="">請先選擇路線</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>目的地</label>
|
|
<select id="dest_select">
|
|
<option value="">請先選擇路線</option>
|
|
</select>
|
|
</div>
|
|
<button onclick="addRoute()">新增路線</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>已儲存路線</h2>
|
|
<div id="routes_list"></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>更新頻率</h2>
|
|
<div class="form-group">
|
|
<label>資料更新間隔</label>
|
|
<div class="slider-container">
|
|
<input type="range" id="interval_slider" min="10" max="120" value="30" step="5"
|
|
oninput="updateIntervalDisplay()">
|
|
<span class="value-display"><span id="interval_value">30</span> 秒</span>
|
|
</div>
|
|
</div>
|
|
<button onclick="saveSettings()">儲存設定</button>
|
|
</div>
|
|
|
|
<div id="status"></div>
|
|
|
|
<script>
|
|
let linesData = [];
|
|
let routesData = [];
|
|
|
|
// Load initial data
|
|
async function init() {
|
|
await loadLines();
|
|
await loadRoutes();
|
|
updateIntervalDisplay();
|
|
}
|
|
|
|
async function loadLines() {
|
|
try {
|
|
const response = await fetch('/api/lines');
|
|
linesData = await response.json();
|
|
const select = document.getElementById('line_select');
|
|
select.innerHTML = '<option value="">請選擇路線</option>';
|
|
linesData.forEach(line => {
|
|
const option = document.createElement('option');
|
|
option.value = line.code;
|
|
option.textContent = line.name;
|
|
select.appendChild(option);
|
|
});
|
|
} catch (err) {
|
|
showStatus('無法載入路線資料', 'error');
|
|
}
|
|
}
|
|
|
|
async function loadRoutes() {
|
|
try {
|
|
const response = await fetch('/api/routes');
|
|
const data = await response.json();
|
|
routesData = data.routes || [];
|
|
document.getElementById('interval_slider').value = data.polling_interval || 30;
|
|
renderRoutes();
|
|
} catch (err) {
|
|
showStatus('無法載入路線', 'error');
|
|
}
|
|
}
|
|
|
|
function updateStations() {
|
|
const lineCode = document.getElementById('line_select').value;
|
|
const stationSelect = document.getElementById('station_select');
|
|
const destSelect = document.getElementById('dest_select');
|
|
|
|
if (!lineCode) {
|
|
stationSelect.innerHTML = '<option value="">請先選擇路線</option>';
|
|
destSelect.innerHTML = '<option value="">請先選擇路線</option>';
|
|
return;
|
|
}
|
|
|
|
const line = linesData.find(l => l.code === lineCode);
|
|
if (!line) return;
|
|
|
|
const stationsHtml = line.stations.map(s =>
|
|
`<option value="${s.code}">${s.name}</option>`
|
|
).join('');
|
|
|
|
stationSelect.innerHTML = '<option value="">請選擇車站</option>' + stationsHtml;
|
|
destSelect.innerHTML = '<option value="">請選擇目的地</option>' + stationsHtml;
|
|
}
|
|
|
|
function renderRoutes() {
|
|
const container = document.getElementById('routes_list');
|
|
if (routesData.length === 0) {
|
|
container.innerHTML = '<p style="color: #666;">尚未設定路線</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = routesData.map((route, index) => `
|
|
<div class="route-item">
|
|
<div class="route-info">
|
|
<span class="route-line" style="background: ${route.line_color}">${route.line_name}</span>
|
|
${route.station_name} → ${route.dest_name}
|
|
</div>
|
|
<button class="danger" style="width: auto; padding: 5px 10px;"
|
|
onclick="removeRoute(${index})">刪除</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
async function addRoute() {
|
|
const lineCode = document.getElementById('line_select').value;
|
|
const stationCode = document.getElementById('station_select').value;
|
|
const destCode = document.getElementById('dest_select').value;
|
|
|
|
if (!lineCode || !stationCode || !destCode) {
|
|
showStatus('請選擇路線、出發站和目的地', 'error');
|
|
return;
|
|
}
|
|
|
|
if (stationCode === destCode) {
|
|
showStatus('出發站和目的地不能相同', 'error');
|
|
return;
|
|
}
|
|
|
|
const line = linesData.find(l => l.code === lineCode);
|
|
const station = line.stations.find(s => s.code === stationCode);
|
|
const dest = line.stations.find(s => s.code === destCode);
|
|
|
|
const route = {
|
|
line_code: lineCode,
|
|
line_name: line.name,
|
|
line_color: line.color,
|
|
station_code: stationCode,
|
|
station_name: station.name,
|
|
dest_code: destCode,
|
|
dest_name: dest.name
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/routes', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(route)
|
|
});
|
|
|
|
if (response.ok) {
|
|
showStatus('路線已新增', 'success');
|
|
await loadRoutes();
|
|
} else {
|
|
const err = await response.text();
|
|
showStatus('新增失敗: ' + err, 'error');
|
|
}
|
|
} catch (err) {
|
|
showStatus('新增失敗', 'error');
|
|
}
|
|
}
|
|
|
|
async function removeRoute(index) {
|
|
try {
|
|
const response = await fetch('/api/routes', {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ index: index })
|
|
});
|
|
|
|
if (response.ok) {
|
|
showStatus('路線已刪除', 'success');
|
|
await loadRoutes();
|
|
} else {
|
|
showStatus('刪除失敗', 'error');
|
|
}
|
|
} catch (err) {
|
|
showStatus('刪除失敗', 'error');
|
|
}
|
|
}
|
|
|
|
function updateIntervalDisplay() {
|
|
const value = document.getElementById('interval_slider').value;
|
|
document.getElementById('interval_value').textContent = value;
|
|
}
|
|
|
|
async function saveSettings() {
|
|
const interval = parseInt(document.getElementById('interval_slider').value);
|
|
|
|
try {
|
|
const response = await fetch('/api/settings', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ polling_interval: interval })
|
|
});
|
|
|
|
if (response.ok) {
|
|
showStatus('設定已儲存', 'success');
|
|
} else {
|
|
showStatus('儲存失敗', 'error');
|
|
}
|
|
} catch (err) {
|
|
showStatus('儲存失敗', 'error');
|
|
}
|
|
}
|
|
|
|
function showStatus(message, type) {
|
|
const statusDiv = document.getElementById('status');
|
|
statusDiv.className = 'status ' + type;
|
|
statusDiv.textContent = message;
|
|
setTimeout(() => {
|
|
statusDiv.className = '';
|
|
statusDiv.textContent = '';
|
|
}, 3000);
|
|
}
|
|
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
)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<WebHandler*>(req->user_ctx);
|
|
|
|
// Get all lines from MTR handler
|
|
std::vector<LineInfo> 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<WebHandler*>(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, "dest_code", route.dest_code.c_str());
|
|
cJSON_AddStringToObject(route_obj, "dest_name", route.dest_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<WebHandler*>(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, "dest_code");
|
|
if (item && cJSON_IsString(item)) route.dest_code = item->valuestring;
|
|
|
|
item = cJSON_GetObjectItem(root, "dest_name");
|
|
if (item && cJSON_IsString(item)) route.dest_name = item->valuestring;
|
|
|
|
cJSON_Delete(root);
|
|
|
|
if (route.line_code.empty() || route.station_code.empty() || route.dest_code.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<WebHandler*>(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<WebHandler*>(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
|