From 71f6800a2ade79c4f855a1c9a439b3254df186fa Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Sun, 16 Aug 2026 11:45:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(display):=20=E5=88=97=E8=BD=A6=E8=BF=9B?= =?UTF-8?q?=E7=AB=99=E5=90=8E=E5=9C=A8=E5=B1=8F=E5=B9=95=E5=8F=B3=E4=BE=A7?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=97=B6=E5=88=BB=E8=A1=A8=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Utils.extractAllStationsFromSchedule() 从时刻表提取所有站点 - 重构 Display.render() 实现左右分栏布局 - 列车存在且屏幕宽度>=50时启用右侧时刻表面板 - 使用颜色区分:已过站(浅灰)/当前站(绿色高亮+>)/未到站(白)/终点站(黄色+[END]) - 版本号升级至 v1.0.64 --- station_broadcast.lua | 218 ++++++++++++++++++++++++++++++------------ 1 file changed, 158 insertions(+), 60 deletions(-) diff --git a/station_broadcast.lua b/station_broadcast.lua index a9f7ec6..6f04ce9 100644 --- a/station_broadcast.lua +++ b/station_broadcast.lua @@ -1,6 +1,6 @@ -- 1. Configuration & Constants -local APP_VERSION_TEXT = "v1.0.63" +local APP_VERSION_TEXT = "v1.0.64" local CONFIG = { CLOUD_URL = "ws://stabadmin.fse-media.group", @@ -1000,6 +1000,34 @@ function Utils.getNextStation(schedule, currentStationName) return nil end +function Utils.extractAllStationsFromSchedule(schedule) + if not schedule or not schedule.entries then return {} end + + local maxIdx = 0 + for k, _ in pairs(schedule.entries) do + if type(k) == "number" and k > maxIdx then maxIdx = k end + end + + local stations = {} + for i = 1, maxIdx do + local entry = schedule.entries[i] + if entry and entry.instruction then + local inst = entry.instruction + if inst.id == "create:station" or inst.id == "create:destination" then + local text = (inst.data and inst.data.text) or inst.text + if type(text) == "string" and text ~= "" then + table.insert(stations, { + index = i, + name = text, + isDestination = inst.id == "create:destination" + }) + end + end + end + end + return stations +end + -- 5. Audio System @@ -2459,15 +2487,22 @@ function Display.render() local mon = Peripherals.primaryMonitor if not mon then return end local w, h = mon.getSize() - local contentBottom = 4 mon.clear() - mon.setCursorPos(1,1) - mon.setTextColor(COLORS.INFO) - mon.write("STATION: " .. State.stationName .. " " .. APP_VERSION_TEXT) - mon.setCursorPos(1,2) - mon.write("CODE: " .. tostring(State.stationCode) .. " P:" .. tostring(State.stationPlatform) .. " " .. tostring(State.stationIdentityMode)) - mon.setCursorPos(1,3) - mon.write("TIME: " .. os.date("%H:%M:%S")) + + local hasSchedulePanel = State.currentTrain ~= nil + local leftW = w + local sepX = nil + local rightX = nil + local rightW = 0 + + if hasSchedulePanel and w >= 50 then + rightW = math.min(math.floor(w * 0.4), 40) + leftW = w - rightW - 1 + sepX = leftW + 1 + rightX = sepX + 1 + leftW = math.max(leftW, 20) + rightW = math.max(rightW, 15) + end local function classifyAudioSource(source, priority) local s = tostring(source or ""):lower() @@ -2477,18 +2512,48 @@ function Display.render() return "Special Announcement" end - local function trunc(str) + local function truncL(str) str = tostring(str or "") - if #str > w then return str:sub(1, w) end + if #str > leftW then return str:sub(1, leftW) end return str end - mon.setCursorPos(1,4) + local function truncR(str) + str = tostring(str or "") + if #str > rightW then return str:sub(1, rightW) end + return str + end + + local function drawLeft(y, text, color) + if y < 1 or y > h then return end + mon.setCursorPos(1, y) + mon.setTextColor(color or COLORS.INFO) + mon.write(truncL(text)) + end + + local function drawRight(y, text, color) + if not rightX or y < 1 or y > h then return end + mon.setCursorPos(rightX, y) + mon.setTextColor(color or COLORS.INFO) + mon.write(truncR(text)) + end + + if sepX then + for y = 1, h do + mon.setCursorPos(sepX, y) + mon.setTextColor(colors.gray) + mon.write("|") + end + end + + drawLeft(1, "STATION: " .. State.stationName .. " " .. APP_VERSION_TEXT, COLORS.INFO) + drawLeft(2, "CODE: " .. tostring(State.stationCode) .. " P:" .. tostring(State.stationPlatform) .. " " .. tostring(State.stationIdentityMode), COLORS.INFO) + drawLeft(3, "TIME: " .. os.date("%H:%M:%S"), COLORS.INFO) + if State.currentPlaying then local p = tonumber(State.currentPlaying.priority) or 0 local kind = classifyAudioSource(State.currentPlaying.source, p) - mon.setTextColor(COLORS.SUCCESS) - mon.write(trunc("AUDIO: " .. kind .. " Playing (P=" .. tostring(p) .. ", Q=" .. tostring(#State.audioQueue) .. ")")) + drawLeft(4, "AUDIO: " .. kind .. " Playing (P=" .. tostring(p) .. ", Q=" .. tostring(#State.audioQueue) .. ")", COLORS.SUCCESS) elseif State.audioRx then local p = tonumber(State.audioRx.priority) or 0 local kind = classifyAudioSource(State.audioRx.source, p) @@ -2496,65 +2561,105 @@ function Display.render() local total = tonumber(State.audioRx.expectedBytes) or 0 local pct = "" if total > 0 then pct = " " .. tostring(math.floor((rx / total) * 100)) .. "%" end - mon.setTextColor(COLORS.DEBUG) - mon.write(trunc("AUDIO: " .. kind .. " Receiving" .. pct .. " (Q=" .. tostring(#State.audioQueue) .. ")")) + drawLeft(4, "AUDIO: " .. kind .. " Receiving" .. pct .. " (Q=" .. tostring(#State.audioQueue) .. ")", COLORS.DEBUG) else - mon.setTextColor(COLORS.INFO) - mon.write(trunc("AUDIO: FREE (Q=" .. tostring(#State.audioQueue) .. ")")) + drawLeft(4, "AUDIO: FREE (Q=" .. tostring(#State.audioQueue) .. ")", COLORS.INFO) end - - -- Current Train + + local contentBottom = 4 + if State.currentTrain then - mon.setCursorPos(1,5) - mon.setTextColor(COLORS.SUCCESS) - mon.write("CURRENT TRAIN:") - mon.setTextColor(COLORS.INFO) - mon.setCursorPos(1,6) - mon.write(State.currentTrain.id) - mon.setCursorPos(1,7) - mon.write("Dest: " .. State.currentTrain.dest) - - -- Countdown + drawLeft(5, "CURRENT TRAIN:", COLORS.SUCCESS) + drawLeft(6, State.currentTrain.id, COLORS.INFO) + drawLeft(7, "Dest: " .. State.currentTrain.dest, COLORS.INFO) + if State.currentTrain.departureTime then local remaining = math.max(0, State.currentTrain.departureTime - os.epoch("utc") / 1000) - mon.setCursorPos(1, 9) if remaining > 0 then - mon.setTextColor(COLORS.WARNING) - mon.write(string.format("Departing in: %02d:%02d", math.floor(remaining/60), math.floor(remaining%60))) + drawLeft(9, string.format("Departing in: %02d:%02d", math.floor(remaining/60), math.floor(remaining%60)), COLORS.WARNING) else - mon.setTextColor(COLORS.ERROR) - mon.write("Departing...") + drawLeft(9, "Departing...", COLORS.ERROR) end contentBottom = 9 else contentBottom = 7 end + + if rightX then + drawRight(1, "SCHEDULE:", COLORS.SUCCESS) + drawRight(2, "Train: " .. State.currentTrain.id, COLORS.INFO) + local scheduleStations = Utils.extractAllStationsFromSchedule(State.currentTrain.schedule) + local currentStationBase = Utils.getBaseName(State.stationName) + local currentStationCode = Utils.getStationCode(State.stationName) + local passedCurrent = false + local stationLineY = 4 + local maxStationLines = h - 3 + + for idx, st in ipairs(scheduleStations) do + if stationLineY > h then break end + local stBase = Utils.getBaseName(st.name) + local stCode = Utils.getStationCode(st.name) + local isCurrentStation = false + if currentStationBase and currentStationBase ~= "" and stBase == currentStationBase then + isCurrentStation = true + elseif currentStationCode and currentStationCode ~= "" and stCode == currentStationCode then + isCurrentStation = true + elseif Utils.isFuzzyMatch(State.stationName, st.name) then + isCurrentStation = true + end + + local prefix = " " + local lineColor = COLORS.INFO + if isCurrentStation then + prefix = "> " + lineColor = COLORS.SUCCESS + passedCurrent = true + elseif passedCurrent then + prefix = " " + else + prefix = "* " + lineColor = colors.lightGray + end + + local suffix = "" + if st.isDestination then + suffix = " [END]" + lineColor = COLORS.WARNING + end + + local displayName = st.name + if isCurrentStation then + displayName = displayName .. " <<" + end + + drawRight(stationLineY, prefix .. displayName .. suffix, lineColor) + stationLineY = stationLineY + 1 + end + + if #scheduleStations == 0 then + drawRight(4, "No station data", COLORS.WARNING) + end + end else - mon.setCursorPos(1,5) - mon.setTextColor(COLORS.WARNING) if Peripherals.station then - mon.write("NO TRAIN AT PLATFORM") + drawLeft(5, "NO TRAIN AT PLATFORM", COLORS.WARNING) else - mon.write("SLAVE NODE MODE") + drawLeft(5, "SLAVE NODE MODE", COLORS.WARNING) end contentBottom = 5 end - - -- Upcoming Trains + local logsStartY = h local upcomingStartY = contentBottom + 1 if Peripherals.blockReader or Peripherals.createTarget then local hasUpcoming = State.upcomingTrains and #State.upcomingTrains > 0 - local maxUpcomingRows = math.max(0, h - upcomingStartY) - mon.setTextColor(COLORS.DEBUG) - mon.setCursorPos(1, upcomingStartY) - mon.write("UPCOMING TRAINS:") + drawLeft(upcomingStartY, "UPCOMING TRAINS:", COLORS.DEBUG) if hasUpcoming then - mon.setTextColor(COLORS.INFO) - local visibleCount = math.min(3, #State.upcomingTrains, math.max(0, maxUpcomingRows - 1)) + local visibleCount = math.min(3, #State.upcomingTrains) for i = 1, visibleCount do + local lineY = upcomingStartY + i + if lineY > h then break end local t = State.upcomingTrains[i] - mon.setCursorPos(1, upcomingStartY + i) local suffix = "" if t.cancelled then suffix = " [CANCEL]" @@ -2570,32 +2675,25 @@ function Display.render() end local etaLabel = tostring(t.etaLabel or (tostring(t.eta or 0) .. "s")) local lineText = string.format("%s%s->%s ETA:%s%s", pfx, t.trainName, t.dest, etaLabel, suffix) - if #lineText > w then lineText = lineText:sub(1, w) end - mon.write(lineText) + drawLeft(lineY, lineText, COLORS.INFO) end logsStartY = math.max(logsStartY, upcomingStartY + visibleCount + 1) else - mon.setTextColor(COLORS.WARNING) local msgY = upcomingStartY + 1 if msgY <= h then - mon.setCursorPos(1, msgY) - mon.write("NONE / WAITING DATA") + drawLeft(msgY, "NONE / WAITING DATA", COLORS.WARNING) logsStartY = math.max(logsStartY, msgY + 1) - else - logsStartY = math.max(logsStartY, msgY) end end end - - -- Logs + local maxLogLines = math.min(3, math.max(0, h - logsStartY + 1)) for i = 1, maxLogLines do local logIdx = i + State.logScrollOffset local entry = State.logs[logIdx] if entry then - mon.setCursorPos(1, logsStartY + i - 1) - mon.setTextColor(COLORS[entry.level] or colors.white) - mon.write(entry.text:sub(1, w)) + local lineY = logsStartY + i - 1 + drawLeft(lineY, entry.text, COLORS[entry.level] or colors.white) end end end