Files
ink-board/main/external/mtr/mtr.cpp
GW_MC c4635948e4 feat(travel): Implement settings UI and web server for MTR route configuration
- 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.
2026-02-03 19:26:53 +08:00

197 lines
5.8 KiB
C++

#include "external/mtr/mtr.h"
#include "external/mtr/line_info.h"
#include "external/mtr/station_info.h"
#include "external/mtr/arrival.h"
#include "assets/MTR_LINE_STATION.h"
#include "network/network.h"
#include "network/http_handler.h"
#include "cJSON.h"
#include "esp_log.h"
#include <string>
#include <stdlib.h>
#include <string.h>
#include "esp_crt_bundle.h"
static const char* TAG = "MTRNextTrainHandler";
// MTR Next Train API endpoint
static const char* MTR_API_BASE = "https://rt.data.gov.hk/v1/transport/mtr/getSchedule.php";
MTRNextTrainHandler::MTRNextTrainHandler() {
ESP_LOGI(TAG, "Initializing MTR Next Train Handler");
mtr_data = cJSON_Parse(MTR_LINE_STATION_JSON);
if (!mtr_data) {
ESP_LOGE(TAG, "Failed to parse MTR line station JSON");
} else {
ESP_LOGI(TAG, "Successfully parsed MTR line station JSON");
}
}
MTRNextTrainHandler::~MTRNextTrainHandler() {
if (mtr_data) {
cJSON_Delete(mtr_data);
mtr_data = nullptr;
}
ESP_LOGI(TAG, "MTR Next Train Handler destroyed");
}
std::vector<LineInfo> MTRNextTrainHandler::get_lines() {
std::vector<LineInfo> lines;
if (!mtr_data) {
ESP_LOGE(TAG, "MTR data not initialized");
return lines;
}
// Iterate through all line objects in the JSON
cJSON* line_json = mtr_data->child;
while (line_json) {
if (cJSON_IsObject(line_json)) {
lines.push_back(LineInfo(line_json));
}
line_json = line_json->next;
}
ESP_LOGI(TAG, "Retrieved %zu MTR lines", lines.size());
return lines;
}
MtrArrivalErrorCode MTRNextTrainHandler::get_next_arrival_info(
NetworkHandler* network_handler,
std::string& line_code,
std::string& station_code,
StationArrivalInfo*& out_info,
Language lang
) {
if (!network_handler) {
ESP_LOGE(TAG, "NetworkHandler is null");
return MtrArrivalErrorCode::UNKNOWN;
}
if (!mtr_data) {
ESP_LOGE(TAG, "MTR data not initialized");
return MtrArrivalErrorCode::UNKNOWN;
}
// Verify line exists
cJSON* line_json = cJSON_GetObjectItem(mtr_data, line_code.c_str());
if (!line_json) {
ESP_LOGW(TAG, "Line not found: %s", line_code.c_str());
return MtrArrivalErrorCode::LINE_NOT_FOUND;
}
// Verify station exists in line
bool station_found = false;
cJSON* stations_json = cJSON_GetObjectItem(line_json, "stations");
if (stations_json && cJSON_IsArray(stations_json)) {
int station_count = cJSON_GetArraySize(stations_json);
for (int i = 0; i < station_count; i++) {
cJSON* station = cJSON_GetArrayItem(stations_json, i);
cJSON* code_json = cJSON_GetObjectItem(station, "code");
if (code_json && cJSON_IsString(code_json)) {
if (station_code == code_json->valuestring) {
station_found = true;
break;
}
}
}
}
if (!station_found) {
ESP_LOGW(TAG, "Station not found: %s in line %s", station_code.c_str(), line_code.c_str());
return MtrArrivalErrorCode::STATION_NOT_FOUND;
}
// Build API URL
std::string url_str = MTR_API_BASE;
url_str += "?line=";
url_str += line_code;
url_str += "&sta=";
url_str += station_code;
if (lang == Language::EN) {
url_str += "&lang=en";
}
ESP_LOGI(TAG, "Fetching arrival info from: %s", url_str.c_str());
// Create HTTP client configuration
esp_http_client_config_t http_config = {};
http_config.url = url_str.c_str();
http_config.timeout_ms = 10000;
http_config.transport_type = HTTP_TRANSPORT_OVER_SSL;
http_config.crt_bundle_attach = esp_crt_bundle_attach;
// Get HTTP handler and perform request
auto http_handler = network_handler->get_http_handler(std::move(http_config));
if (!http_handler) {
ESP_LOGE(TAG, "Failed to create HTTP handler");
return MtrArrivalErrorCode::UNKNOWN;
}
esp_err_t err = http_handler->perform_request();
if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
return MtrArrivalErrorCode::NO_ARRIVAL_INFO;
}
// Get response body
char* buffer = nullptr;
int total_len = 0;
http_handler->get_body(buffer, total_len);
if (!buffer || total_len <= 0) {
ESP_LOGE(TAG, "Empty response from MTR API");
if (buffer) {
free(buffer);
}
return MtrArrivalErrorCode::NO_ARRIVAL_INFO;
}
ESP_LOGI(TAG, "Received %d bytes from MTR API", total_len);
ESP_LOGI(TAG, "Parsing full API response");
cJSON* root_json = cJSON_Parse(buffer);
delete[] buffer;
if (!root_json) {
const char* error_ptr = cJSON_GetErrorPtr();
if (error_ptr) {
ESP_LOGE(TAG, "Failed to parse MTR API response at position: %s", error_ptr);
} else {
ESP_LOGE(TAG, "Failed to parse MTR API response - unknown error");
}
return MtrArrivalErrorCode::NO_ARRIVAL_INFO;
}
cJSON* data_json = cJSON_GetObjectItem(root_json, "data");
if (!data_json) {
ESP_LOGE(TAG, "Could not find 'data' object in response");
cJSON_Delete(root_json);
return MtrArrivalErrorCode::NO_ARRIVAL_INFO;
}
std::string station_key = line_code + "-" + station_code;
cJSON* station_json = cJSON_GetObjectItem(data_json, station_key.c_str());
if (!station_json) {
ESP_LOGE(TAG, "Could not find station key '%s' in data object", station_key.c_str());
cJSON_Delete(root_json);
return MtrArrivalErrorCode::NO_ARRIVAL_INFO;
}
cJSON* status_json = cJSON_GetObjectItem(root_json, "status");
if (status_json && cJSON_IsNumber(status_json)) {
cJSON_AddItemToObject(station_json, "status", cJSON_Duplicate(status_json, 1));
}
cJSON* message_json = cJSON_GetObjectItem(root_json, "message");
if (message_json && cJSON_IsString(message_json)) {
cJSON_AddItemToObject(station_json, "message", cJSON_Duplicate(message_json, 1));
}
out_info = new StationArrivalInfo(mtr_data, station_json, line_code, station_code);
cJSON_Delete(root_json);
ESP_LOGI(TAG, "Successfully retrieved arrival info for %s/%s", line_code.c_str(), station_code.c_str());
return MtrArrivalErrorCode::NONE;
}