#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 #include #include #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 MTRNextTrainHandler::get_lines() { std::vector 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 = 15000; http_config.transport_type = HTTP_TRANSPORT_OVER_SSL; http_config.crt_bundle_attach = esp_crt_bundle_attach; // Retry logic for connection failures constexpr int MAX_RETRIES = 2; esp_err_t err = ESP_FAIL; char* buffer = nullptr; int total_len = 0; for (int retry = 0; retry <= MAX_RETRIES; retry++) { if (retry > 0) { ESP_LOGW(TAG, "Retrying HTTP request (%d/%d)", retry, MAX_RETRIES); vTaskDelay(pdMS_TO_TICKS(500)); } // Create HTTP client configuration for each attempt esp_http_client_config_t http_config = {}; http_config.url = url_str.c_str(); http_config.timeout_ms = 15000; 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"); continue; } err = http_handler->perform_request(); if (err != ESP_OK) { ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); continue; } // Get response body http_handler->get_body(buffer, total_len); if (buffer && total_len > 0) { break; } if (buffer) { free(buffer); buffer = nullptr; } } if (err != ESP_OK || !buffer || total_len <= 0) { ESP_LOGE(TAG, "Failed to get response after retries"); 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; }