diff --git a/main/external/mtr/arrival.cpp b/main/external/mtr/arrival.cpp new file mode 100644 index 0000000..0fe398d --- /dev/null +++ b/main/external/mtr/arrival.cpp @@ -0,0 +1,98 @@ +#include "external/mtr/arrival.h" +#include "cJSON.h" +#include "esp_log.h" +#include + +static const char* TAG = "StationArrivalInfo"; + +StationArrivalInfo::StationArrivalInfo( + cJSON* mtr_line_station_json, + cJSON* arrival_json, + const std::string& train_line_code, + const std::string& train_station_code +) : _status(UNKNOWN_STATUS) +, _train_line(train_line_code) +, _train_station(train_station_code) { + + if (!arrival_json) { + ESP_LOGE(TAG, "arrival_json is null"); + _status = FAILED_WITH_MESSAGE; + _message = "No arrival data received"; + return; + } + + // Parse status + cJSON* status_json = cJSON_GetObjectItem(arrival_json, "status"); + if (status_json && cJSON_IsNumber(status_json)) { + int status_value = status_json->valueint; + if (status_value >= 0 && status_value <= 3) { + _status = static_cast(status_value); + } + } + + // TODO: verify the arrival json parsing + + // Parse message (if present) + cJSON* message_json = cJSON_GetObjectItem(arrival_json, "message"); + if (message_json && cJSON_IsString(message_json)) { + _message = message_json->valuestring; + } + + // Parse UP direction arrivals + cJSON* up_json = cJSON_GetObjectItem(arrival_json, "UP"); + if (up_json && cJSON_IsArray(up_json)) { + int up_count = cJSON_GetArraySize(up_json); + for (int i = 0; i < up_count; i++) { + cJSON* arrival_item = cJSON_GetArrayItem(up_json, i); + if (arrival_item) { + std::string time_str = ""; + std::string dest_str = ""; + + cJSON* time_json = cJSON_GetObjectItem(arrival_item, "time"); + if (time_json && cJSON_IsString(time_json)) { + time_str = time_json->valuestring; + } + + cJSON* dest_json = cJSON_GetObjectItem(arrival_item, "dest"); + if (dest_json && cJSON_IsString(dest_json)) { + dest_str = dest_json->valuestring; + } + + if (!time_str.empty()) { + _up_arrivals.emplace_back(time_str, dest_str); + } + } + } + } + + // Parse DOWN direction arrivals + cJSON* down_json = cJSON_GetObjectItem(arrival_json, "DOWN"); + if (down_json && cJSON_IsArray(down_json)) { + int down_count = cJSON_GetArraySize(down_json); + for (int i = 0; i < down_count; i++) { + cJSON* arrival_item = cJSON_GetArrayItem(down_json, i); + if (arrival_item) { + std::string time_str = ""; + std::string dest_str = ""; + + cJSON* time_json = cJSON_GetObjectItem(arrival_item, "time"); + if (time_json && cJSON_IsString(time_json)) { + time_str = time_json->valuestring; + } + + cJSON* dest_json = cJSON_GetObjectItem(arrival_item, "dest"); + if (dest_json && cJSON_IsString(dest_json)) { + dest_str = dest_json->valuestring; + } + + if (!time_str.empty()) { + _down_arrivals.emplace_back(time_str, dest_str); + } + } + } + } + + ESP_LOGI(TAG, "Parsed arrival info for %s/%s: %zu UP, %zu DOWN trains", + train_line_code.c_str(), train_station_code.c_str(), + _up_arrivals.size(), _down_arrivals.size()); +} diff --git a/main/external/mtr/arrival.h b/main/external/mtr/arrival.h index b15ae9d..a17abe1 100644 --- a/main/external/mtr/arrival.h +++ b/main/external/mtr/arrival.h @@ -21,6 +21,10 @@ public: return _arrival_time.c_str(); } + const char* destination() const { + return _destination_name.c_str(); + } + private: const std::string _arrival_time; const std::string _destination_name; // not the code of the station @@ -38,6 +42,14 @@ struct StationArrivalInfo { public: friend class MTRNextTrainHandler; + // Public accessors + StatusEnum status() const { return _status; } + const char* message() const { return _message.c_str(); } + const char* train_line() const { return _train_line.c_str(); } + const char* train_station() const { return _train_station.c_str(); } + const std::vector* up_arrivals() const { return &_up_arrivals; } + const std::vector* down_arrivals() const { return &_down_arrivals; } + private: StationArrivalInfo( cJSON* mtr_line_station_json, diff --git a/main/external/mtr/line_info.cpp b/main/external/mtr/line_info.cpp new file mode 100644 index 0000000..153789b --- /dev/null +++ b/main/external/mtr/line_info.cpp @@ -0,0 +1,45 @@ +#include "external/mtr/line_info.h" +#include "external/mtr/station_info.h" +#include "cJSON.h" +#include "esp_log.h" + +LineInfo::LineInfo(cJSON* line_json) { + if (!line_json) { + ESP_LOGE(LINE_INFO_TAG, "line_json is null"); + return; + } + + // Parse line code + cJSON* code_json = cJSON_GetObjectItem(line_json, "code"); + if (code_json && cJSON_IsString(code_json)) { + _code = code_json->valuestring; + } else { + ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'code' field"); + } + + // Parse line color (note: field is 'line_color' in JSON, not 'color') + cJSON* color_json = cJSON_GetObjectItem(line_json, "line_color"); + if (color_json && cJSON_IsString(color_json)) { + _color = color_json->valuestring; + } else { + ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'line_color' field"); + } + + // Parse stations array + cJSON* stations_json = cJSON_GetObjectItem(line_json, "stations"); + if (stations_json && cJSON_IsArray(stations_json)) { + int station_count = cJSON_GetArraySize(stations_json); + _stations.reserve(station_count); + + for (int i = 0; i < station_count; i++) { + cJSON* station_json = cJSON_GetArrayItem(stations_json, i); + if (station_json) { + _stations.emplace_back(station_json); + } + } + + ESP_LOGI(LINE_INFO_TAG, "Created LineInfo: %s with %d stations", _code.c_str(), station_count); + } else { + ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'stations' array"); + } +} diff --git a/main/external/mtr/line_info.h b/main/external/mtr/line_info.h index 2eedb27..a72cad9 100644 --- a/main/external/mtr/line_info.h +++ b/main/external/mtr/line_info.h @@ -4,8 +4,9 @@ #include "external/mtr/station_info.h" #include "external/mtr/mtr.h" #include +#include -static const char* LINE_INFO_TAG = "LineInfo"; +#define LINE_INFO_TAG "LineInfo" // Forward declaration class MTRNextTrainHandler; diff --git a/main/external/mtr/mtr.cpp b/main/external/mtr/mtr.cpp new file mode 100644 index 0000000..8a15f65 --- /dev/null +++ b/main/external/mtr/mtr.cpp @@ -0,0 +1,167 @@ +#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 + +static const char* TAG = "MTRNextTrainHandler"; + +// MTR Next Train API endpoint +// Note: This is a placeholder - replace with actual MTR 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::ostringstream url; + url << MTR_API_BASE << "?line=" << line_code << "&sta=" << station_code; + if (lang == Language::EN) { + url << "&lang=en"; + } + + std::string url_str = url.str(); + 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.use_global_ca_store = true; + http_config.skip_cert_common_name_check = false; + + // 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_LOGD(TAG, "Response: %s", buffer); + + // Parse JSON response + cJSON* arrival_json = cJSON_Parse(buffer); + free(buffer); + + if (!arrival_json) { + ESP_LOGE(TAG, "Failed to parse MTR API response"); + return MtrArrivalErrorCode::NO_ARRIVAL_INFO; + } + + // Create StationArrivalInfo object + out_info = new StationArrivalInfo(mtr_data, arrival_json, line_code, station_code); + + cJSON_Delete(arrival_json); + + ESP_LOGI(TAG, "Successfully retrieved arrival info for %s/%s", line_code.c_str(), station_code.c_str()); + return MtrArrivalErrorCode::NONE; +} diff --git a/main/external/mtr/mtr.h b/main/external/mtr/mtr.h index a7492d1..0cc09b7 100644 --- a/main/external/mtr/mtr.h +++ b/main/external/mtr/mtr.h @@ -1,4 +1,6 @@ -#include "assets/mtr_line_station_json.h" +#pragma once + +#include "assets/MTR_LINE_STATION.h" #include "cJSON.h" #include #include "esp_log.h" @@ -45,8 +47,8 @@ public: NetworkHandler* network_handler, std::string& line_code, std::string& station_code, - Language lang = Language::TC, - StationArrivalInfo*& out_info + StationArrivalInfo*& out_info, + Language lang = Language::TC ); private: diff --git a/main/external/mtr/station_info.cpp b/main/external/mtr/station_info.cpp new file mode 100644 index 0000000..b9155ed --- /dev/null +++ b/main/external/mtr/station_info.cpp @@ -0,0 +1,28 @@ +#include "external/mtr/station_info.h" +#include "cJSON.h" +#include "esp_log.h" + +StationInfo::StationInfo(cJSON* station_json) { + if (!station_json) { + ESP_LOGE(STATION_INFO_TAG, "station_json is null"); + return; + } + + // Parse station code + cJSON* code_json = cJSON_GetObjectItem(station_json, "code"); + if (code_json && cJSON_IsString(code_json)) { + _code = code_json->valuestring; + } else { + ESP_LOGW(STATION_INFO_TAG, "Missing or invalid 'code' field"); + } + + // Parse station name + cJSON* name_json = cJSON_GetObjectItem(station_json, "name"); + if (name_json && cJSON_IsString(name_json)) { + _name = name_json->valuestring; + } else { + ESP_LOGW(STATION_INFO_TAG, "Missing or invalid 'name' field"); + } + + ESP_LOGD(STATION_INFO_TAG, "Created StationInfo: %s (%s)", _name.c_str(), _code.c_str()); +} diff --git a/main/external/mtr/station_info.h b/main/external/mtr/station_info.h index 6c75522..914e3b1 100644 --- a/main/external/mtr/station_info.h +++ b/main/external/mtr/station_info.h @@ -1,8 +1,9 @@ #pragma once #include "esp_log.h" #include "external/mtr/line_info.h" +#include -static const char* STATION_INFO_TAG = "StationInfo"; +#define STATION_INFO_TAG "StationInfo" // Forward declaration struct LineInfo;