feat(travel): Refactor route handling to use direction instead of destination

This commit is contained in:
GW_MC
2026-02-03 20:11:16 +08:00
parent c4635948e4
commit 3617a206ff
9 changed files with 137 additions and 57 deletions

View File

@@ -1,7 +1,5 @@
#pragma once
#include "external/mtr/arrival.h"
#include "cJSON.h"
#include "external/mtr/mtr.h"
#include <string>
#include <vector>

View File

@@ -51,3 +51,12 @@ LineInfo::LineInfo(cJSON* line_json) {
ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'stations' array");
}
}
const char* LineInfo::get_station_name(const std::string& station_code) const {
for (const auto& station : _stations) {
if (std::string(station.code()) == station_code) {
return station.name();
}
}
return "";
}

View File

@@ -35,6 +35,8 @@ public:
return &_stations;
}
const char* get_station_name(const std::string& station_code) const;
friend class MTRNextTrainHandler;
private:

View File

@@ -28,7 +28,8 @@ void SettingHandler::load_settings() {
}
}
// Load routes
// Load routes - clear existing settings as per new format
routes_.clear();
std::string routes_json = storage_->get(NVS_KEY_ROUTES);
if (!routes_json.empty()) {
routes_from_json(routes_json);
@@ -65,7 +66,7 @@ void SettingHandler::add_route(const RoutePair& route) {
}
routes_.push_back(route);
ESP_LOGI(TAG, "Added route: %s -> %s", route.station_name.c_str(), route.dest_name.c_str());
ESP_LOGI(TAG, "Added route: %s -> %s", route.station_name.c_str(), route.direction_name.c_str());
}
void SettingHandler::remove_route(size_t index) {
@@ -100,8 +101,8 @@ std::string SettingHandler::routes_to_json() const {
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_AddStringToObject(route_obj, "direction", route.direction.c_str());
cJSON_AddStringToObject(route_obj, "direction_name", route.direction_name.c_str());
cJSON_AddItemToArray(root, route_obj);
}
@@ -152,13 +153,13 @@ void SettingHandler::routes_from_json(const std::string& json) {
item = cJSON_GetObjectItem(route_obj, "station_name");
if (item && cJSON_IsString(item)) route.station_name = item->valuestring;
item = cJSON_GetObjectItem(route_obj, "dest_code");
if (item && cJSON_IsString(item)) route.dest_code = item->valuestring;
item = cJSON_GetObjectItem(route_obj, "direction");
if (item && cJSON_IsString(item)) route.direction = item->valuestring;
item = cJSON_GetObjectItem(route_obj, "dest_name");
if (item && cJSON_IsString(item)) route.dest_name = item->valuestring;
item = cJSON_GetObjectItem(route_obj, "direction_name");
if (item && cJSON_IsString(item)) route.direction_name = item->valuestring;
if (!route.line_code.empty() && !route.station_code.empty() && !route.dest_code.empty()) {
if (!route.line_code.empty() && !route.station_code.empty() && !route.direction.empty()) {
routes_.push_back(route);
}
}

View File

@@ -6,7 +6,7 @@
namespace travel {
/**
* @brief Structure representing a monitored route (station -> destination pair)
* @brief Structure representing a monitored route (station -> direction pair)
*/
struct RoutePair {
std::string line_code; // Line code (e.g., "ISL", "TWL")
@@ -14,13 +14,13 @@ struct RoutePair {
std::string line_color; // Hex color code (e.g., "#007DC5")
std::string station_code; // Station code (e.g., "CEN")
std::string station_name; // Station name in TC (e.g., "中環")
std::string dest_code; // Destination station code (e.g., "CHW")
std::string dest_name; // Destination station name in TC (e.g., "柴灣")
std::string direction; // Direction terminal code (e.g., "CHW" for "柴灣方向")
std::string direction_name; // Direction name in TC (e.g., "柴灣方向")
bool operator==(const RoutePair& other) const {
return line_code == other.line_code &&
station_code == other.station_code &&
dest_code == other.dest_code;
direction == other.direction;
}
};
@@ -28,8 +28,9 @@ struct RoutePair {
* @brief Structure representing a single arrival display entry
*/
struct ArrivalDisplay {
std::string arrival_time; // Formatted arrival time (e.g., "2分鐘", "14:32")
std::string destination; // Destination station name
std::string arrival_time; // Formatted arrival time (e.g., "2分鐘")
std::string arrival_time_full; // Full time (e.g., "14:32") if available
std::string direction; // Direction terminal name (e.g., "柴灣方向")
std::string platform; // Platform number if available
};
@@ -38,7 +39,7 @@ struct ArrivalDisplay {
*/
struct RouteArrivalData {
RoutePair route;
std::vector<ArrivalDisplay> arrivals; // List of upcoming trains to destination
std::vector<ArrivalDisplay> arrivals; // List of upcoming trains in direction
bool is_valid = false;
std::string error_message;
};

View File

@@ -179,7 +179,7 @@ void MainUI::update_route_display_(RouteDisplay& display, const RouteArrivalData
lv_obj_clear_flag(display.container, LV_OBJ_FLAG_HIDDEN);
// Update header with line color
std::string header_text = data.route.station_name + "" + data.route.dest_name;
std::string header_text = data.route.station_name + "" + data.route.direction_name;
lv_label_set_text(display.header, header_text.c_str());
if (!data.route.line_color.empty()) {
@@ -191,7 +191,11 @@ void MainUI::update_route_display_(RouteDisplay& display, const RouteArrivalData
for (int i = 0; i < MAX_ARRIVALS_PER_ROUTE; i++) {
if (i < static_cast<int>(data.arrivals.size())) {
const auto& arrival = data.arrivals[i];
std::string arrival_text = " " + arrival.arrival_time + "" + arrival.destination;
std::string arrival_text = " " + arrival.arrival_time;
if (!arrival.arrival_time_full.empty()) {
arrival_text += " (" + arrival.arrival_time_full + ")";
}
arrival_text += " " + arrival.direction;
lv_label_set_text(display.arrival_labels[i], arrival_text.c_str());
lv_obj_clear_flag(display.arrival_labels[i], LV_OBJ_FLAG_HIDDEN);
} else {

View File

@@ -1,6 +1,6 @@
#include "ui/apps/travel/ui/main_handler.h"
#include "display/lvgl_handler.h"
#include "external/mtr/arrival.h"
#include "external/mtr/line_info.h"
#include "esp_log.h"
#include <ctime>
#include <iomanip>
@@ -153,28 +153,65 @@ void MainUIHandler::fetch_and_update_arrivals_() {
);
if (error == MtrArrivalErrorCode::NONE && arrival_info) {
// Filter arrivals going to our destination
// Determine which direction (UP or DOWN) to filter based on terminal station
const auto* up_arrivals = arrival_info->up_arrivals();
const auto* down_arrivals = arrival_info->down_arrivals();
// Check both UP and DOWN directions for trains to our destination
auto filter_arrivals = [&](const std::vector<ArrivalInfo>* arrivals) {
// Get all lines to find station positions
std::vector<LineInfo> lines = mtr_handler_->get_lines();
// Find current line and determine direction
bool filter_up = false;
bool filter_down = false;
for (const auto& line : lines) {
if (std::string(line.code()) == line_code) {
const auto* stations = line.stations();
if (stations) {
// Find index of current station and terminal station
size_t current_idx = SIZE_MAX;
size_t terminal_idx = SIZE_MAX;
for (size_t i = 0; i < stations->size(); i++) {
if (std::string(stations->at(i).code()) == station_code) {
current_idx = i;
}
if (std::string(stations->at(i).code()) == route.direction) {
terminal_idx = i;
}
}
// Determine direction: if terminal is at higher index, it's DOWN
if (current_idx != SIZE_MAX && terminal_idx != SIZE_MAX) {
if (terminal_idx > current_idx) {
filter_down = true;
} else {
filter_up = true;
}
}
}
break;
}
}
// Filter arrivals based on direction
auto process_arrivals = [&](const std::vector<ArrivalInfo>* arrivals) {
if (!arrivals) return;
for (const auto& arrival : *arrivals) {
// Check if this train goes to our destination
std::string dest = arrival.destination();
if (dest.find(route.dest_name) != std::string::npos ||
dest.find(route.dest_code) != std::string::npos) {
ArrivalDisplay display;
display.arrival_time = format_arrival_time_(arrival.arrival_time());
display.destination = dest;
display.arrival_time_full = format_arrival_time_full_(arrival.arrival_time());
display.direction = route.direction_name;
data.arrivals.push_back(display);
}
}
};
filter_arrivals(up_arrivals);
filter_arrivals(down_arrivals);
if (filter_up) {
process_arrivals(up_arrivals);
}
if (filter_down) {
process_arrivals(down_arrivals);
}
data.is_valid = true;
@@ -224,6 +261,21 @@ std::string MainUIHandler::format_arrival_time_(const std::string& api_time) {
return api_time;
}
std::string MainUIHandler::format_arrival_time_full_(const std::string& api_time) {
// Returns absolute time for display (e.g., "14:32")
// Returns empty string for relative times
if (api_time.length() <= 2) {
return "";
}
size_t t_pos = api_time.find('T');
if (t_pos != std::string::npos && api_time.length() > t_pos + 5) {
return api_time.substr(t_pos + 1, 5);
}
return "";
}
std::string MainUIHandler::get_current_time_string_() {
auto now = std::time(nullptr);
auto tm = *std::localtime(&now);

View File

@@ -3,6 +3,7 @@
#include "ui/apps/travel/ui/main.h"
#include "ui/apps/travel/settings/settings_handler.h"
#include "ui/interaction_handler.h"
#include "external/mtr/arrival.h"
#include "external/mtr/mtr.h"
#include "network/network.h"
#include "freertos/FreeRTOS.h"
@@ -47,6 +48,7 @@ private:
void on_settings_button_clicked_();
void fetch_and_update_arrivals_();
std::string format_arrival_time_(const std::string& api_time);
std::string format_arrival_time_full_(const std::string& api_time);
std::string get_current_time_string_();
std::unique_ptr<MainUI> main_ui_;

View File

@@ -256,8 +256,8 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
</select>
</div>
<div class="form-group">
<label></label>
<select id="dest_select">
<label></label>
<select id="direction_select">
<option value=""></option>
</select>
</div>
@@ -327,11 +327,11 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
function updateStations() {
const lineCode = document.getElementById('line_select').value;
const stationSelect = document.getElementById('station_select');
const destSelect = document.getElementById('dest_select');
const directionSelect = document.getElementById('direction_select');
if (!lineCode) {
stationSelect.innerHTML = '<option value=""></option>';
destSelect.innerHTML = '<option value=""></option>';
directionSelect.innerHTML = '<option value=""></option>';
return;
}
@@ -343,7 +343,18 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
).join('');
stationSelect.innerHTML = '<option value=""></option>' + stationsHtml;
destSelect.innerHTML = '<option value=""></option>' + stationsHtml;
const stations = line.stations;
if (stations.length >= 2) {
const firstStation = stations[0];
const lastStation = stations[stations.length - 1];
const directionsHtml =
`<option value="${firstStation.code}">${firstStation.name}</option>` +
`<option value="${lastStation.code}">${lastStation.name}</option>`;
directionSelect.innerHTML = '<option value=""></option>' + directionsHtml;
} else {
directionSelect.innerHTML = '<option value=""></option>';
}
}
function renderRoutes() {
@@ -357,7 +368,7 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
<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}
${route.station_name} ${route.direction_name}
</div>
<button class="danger" style="width: auto; padding: 5px 10px;"
onclick="removeRoute(${index})"></button>
@@ -368,21 +379,21 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
async function addRoute() {
const lineCode = document.getElementById('line_select').value;
const stationCode = document.getElementById('station_select').value;
const destCode = document.getElementById('dest_select').value;
const directionCode = document.getElementById('direction_select').value;
if (!lineCode || !stationCode || !destCode) {
showStatus('', 'error');
if (!lineCode || !stationCode || !directionCode) {
showStatus('', 'error');
return;
}
if (stationCode === destCode) {
showStatus('', 'error');
if (stationCode === directionCode) {
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 direction = line.stations.find(s => s.code === directionCode);
const route = {
line_code: lineCode,
@@ -390,8 +401,8 @@ esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) {
line_color: line.color,
station_code: stationCode,
station_name: station.name,
dest_code: destCode,
dest_name: dest.name
direction: directionCode,
direction_name: direction.name + ''
};
try {
@@ -536,8 +547,8 @@ esp_err_t WebHandler::get_routes_handler_(httpd_req_t* req) {
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_AddStringToObject(route_obj, "direction", route.direction.c_str());
cJSON_AddStringToObject(route_obj, "direction_name", route.direction_name.c_str());
cJSON_AddItemToArray(routes_arr, route_obj);
}
cJSON_AddItemToObject(root, "routes", routes_arr);
@@ -603,15 +614,15 @@ esp_err_t WebHandler::add_route_handler_(httpd_req_t* req) {
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, "direction");
if (item && cJSON_IsString(item)) route.direction = item->valuestring;
item = cJSON_GetObjectItem(root, "dest_name");
if (item && cJSON_IsString(item)) route.dest_name = item->valuestring;
item = cJSON_GetObjectItem(root, "direction_name");
if (item && cJSON_IsString(item)) route.direction_name = item->valuestring;
cJSON_Delete(root);
if (route.line_code.empty() || route.station_code.empty() || route.dest_code.empty()) {
if (route.line_code.empty() || route.station_code.empty() || route.direction.empty()) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing required fields");
return ESP_FAIL;
}