Squash of branch setup
This commit is contained in:
98
main/external/mtr/arrival.cpp
vendored
Normal file
98
main/external/mtr/arrival.cpp
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "external/mtr/arrival.h"
|
||||
#include "cJSON.h"
|
||||
#include "esp_log.h"
|
||||
#include <string>
|
||||
|
||||
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<StatusEnum>(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());
|
||||
}
|
||||
67
main/external/mtr/arrival.h
vendored
Normal file
67
main/external/mtr/arrival.h
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include "external/mtr/arrival.h"
|
||||
#include "cJSON.h"
|
||||
#include "external/mtr/mtr.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Forward declaration
|
||||
class MTRNextTrainHandler;
|
||||
|
||||
struct ArrivalInfo {
|
||||
public:
|
||||
// Caller transfers ownership of arrival_time to ArrivalInfo
|
||||
ArrivalInfo(
|
||||
const std::string& arrival_time,
|
||||
const std::string& destination_name
|
||||
) : _arrival_time(arrival_time)
|
||||
, _destination_name(destination_name) { }
|
||||
|
||||
const char* arrival_time() const {
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
enum StatusEnum {
|
||||
SUCCESSFUL_WITHOUT_DELAY = 0,
|
||||
SUCCESSFUL_WITH_DELAY = 1,
|
||||
FAILED_WITH_MESSAGE = 2,
|
||||
UNKNOWN_STATUS = 3
|
||||
};
|
||||
|
||||
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<ArrivalInfo>* up_arrivals() const { return &_up_arrivals; }
|
||||
const std::vector<ArrivalInfo>* down_arrivals() const { return &_down_arrivals; }
|
||||
|
||||
private:
|
||||
StationArrivalInfo(
|
||||
cJSON* mtr_line_station_json,
|
||||
cJSON* arrival_json,
|
||||
const std::string& train_line_code,
|
||||
const std::string& train_station_code
|
||||
);
|
||||
|
||||
StatusEnum _status;
|
||||
std::string _message; // only valid if status == FAILED_WITH_MESSAGE
|
||||
std::string _train_line;
|
||||
std::string _train_station;
|
||||
std::vector<ArrivalInfo> _up_arrivals;
|
||||
std::vector<ArrivalInfo> _down_arrivals;
|
||||
};
|
||||
45
main/external/mtr/line_info.cpp
vendored
Normal file
45
main/external/mtr/line_info.cpp
vendored
Normal file
@@ -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");
|
||||
}
|
||||
}
|
||||
46
main/external/mtr/line_info.h
vendored
Normal file
46
main/external/mtr/line_info.h
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include "cJSON.h"
|
||||
#include "esp_log.h"
|
||||
#include "external/mtr/station_info.h"
|
||||
#include "external/mtr/mtr.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define LINE_INFO_TAG "LineInfo"
|
||||
|
||||
// Forward declaration
|
||||
class MTRNextTrainHandler;
|
||||
struct StationInfo;
|
||||
|
||||
struct LineInfo {
|
||||
public:
|
||||
|
||||
// caller does not own the returned char pointers
|
||||
const char* code() const {
|
||||
return _code.c_str();
|
||||
}
|
||||
// caller does not own the returned char pointers
|
||||
const char* color() const {
|
||||
return _color.c_str();
|
||||
}
|
||||
size_t station_count() const {
|
||||
return _stations.size();
|
||||
}
|
||||
// caller does not own the returned array or StationInfo pointers
|
||||
const std::vector<StationInfo>* stations() const {
|
||||
return &_stations;
|
||||
}
|
||||
|
||||
friend class MTRNextTrainHandler;
|
||||
|
||||
private:
|
||||
// Caller transfers ownership of stations array and its contents to LineInfo
|
||||
LineInfo(
|
||||
cJSON* line_json
|
||||
);
|
||||
|
||||
std::string _code;
|
||||
std::string _color;
|
||||
std::vector<StationInfo> _stations;
|
||||
};
|
||||
|
||||
167
main/external/mtr/mtr.cpp
vendored
Normal file
167
main/external/mtr/mtr.cpp
vendored
Normal file
@@ -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 <string>
|
||||
#include <sstream>
|
||||
|
||||
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<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::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;
|
||||
}
|
||||
58
main/external/mtr/mtr.h
vendored
Normal file
58
main/external/mtr/mtr.h
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "assets/MTR_LINE_STATION.h"
|
||||
#include "cJSON.h"
|
||||
#include <string>
|
||||
#include "esp_log.h"
|
||||
#include "external/mtr/line_info.h"
|
||||
#include <vector>
|
||||
#include "network/network.h"
|
||||
|
||||
// Forward declaration
|
||||
struct StationArrivalInfo;
|
||||
struct LineInfo;
|
||||
|
||||
enum class MtrArrivalErrorCode {
|
||||
NONE = 0,
|
||||
LINE_NOT_FOUND = 1,
|
||||
STATION_NOT_FOUND = 2,
|
||||
NO_ARRIVAL_INFO = 3,
|
||||
UNKNOWN = 99,
|
||||
};
|
||||
|
||||
enum class Language {
|
||||
EN,
|
||||
TC,
|
||||
};
|
||||
|
||||
class MTRNextTrainHandler {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct a new MTR Next Train Handler object
|
||||
* @param json Pointer to cJSON object containing MTR Next Train data
|
||||
*
|
||||
* > Caller transfers ownership of the cJSON object to MTRNextTrainHandler
|
||||
*
|
||||
* cJSON structure for MTR Next Train data
|
||||
* This structure is used to parse and store the MTR Next Train JSON data.
|
||||
* Record<code name string, {name: string, code: string, color: hex string, station: {code: string, name: string}[]}>
|
||||
*/
|
||||
MTRNextTrainHandler();
|
||||
~MTRNextTrainHandler();
|
||||
|
||||
std::vector<LineInfo> get_lines();
|
||||
|
||||
MtrArrivalErrorCode get_next_arrival_info(
|
||||
NetworkHandler* network_handler,
|
||||
std::string& line_code,
|
||||
std::string& station_code,
|
||||
StationArrivalInfo*& out_info,
|
||||
Language lang = Language::TC
|
||||
);
|
||||
|
||||
private:
|
||||
cJSON* mtr_data;
|
||||
};
|
||||
|
||||
|
||||
28
main/external/mtr/station_info.cpp
vendored
Normal file
28
main/external/mtr/station_info.cpp
vendored
Normal file
@@ -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());
|
||||
}
|
||||
27
main/external/mtr/station_info.h
vendored
Normal file
27
main/external/mtr/station_info.h
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "esp_log.h"
|
||||
#include "external/mtr/line_info.h"
|
||||
#include <string>
|
||||
|
||||
#define STATION_INFO_TAG "StationInfo"
|
||||
|
||||
// Forward declaration
|
||||
struct LineInfo;
|
||||
|
||||
struct StationInfo {
|
||||
public:
|
||||
StationInfo(cJSON* station_json);
|
||||
|
||||
const char* name() const { return _name.c_str(); }
|
||||
const char* code() const { return _code.c_str(); }
|
||||
|
||||
friend class LineInfo;
|
||||
|
||||
private:
|
||||
// Caller transfers ownership of station_name and station_code to StationInfo
|
||||
StationInfo(std::string& station_name, std::string& station_code)
|
||||
: _name(station_name), _code(station_code) { }
|
||||
|
||||
std::string _name;
|
||||
std::string _code;
|
||||
};
|
||||
Reference in New Issue
Block a user