fix(data): 修复NBT(BlockReader/CreateTarget)与车站时刻表数据源不一致导致终点站广播漏播的问题

- 新增 looksLikeValidDestinationText: 检测目的地/车次名是否有效(过滤Unknown和高比例问号乱码)
- 新增 tryCorrectUpcomingEntryWithCurrentTrain: 用权威State.currentTrain数据校正 upcoming 条目中的乱码车次/目的地
- 新增 checkIsAtStationWithCurrentTrain: 增强 isAtStation 匹配鲁棒性, 优先站台号+车次ID匹配, 乱码时降级到目的地模糊匹配
- 提前预校正: 在生成 announceKey 前就校正乱码字段, 确保 approaching/alarm 冷却去重 key 一致
- 后置全局校正: 在赋值 State.upcomingTrains 前再次对所有条目做权威校正, 保证显示和广播使用的终点站信息一致

版本升级至 v1.0.70
This commit is contained in:
2026-08-16 12:44:09 +08:00
parent 32b2c8ce9d
commit 17b03ce0f7
+118 -88
View File
@@ -3209,110 +3209,90 @@ function Main.loop()
if type(stop.Stopovers) == "table" and #stop.Stopovers > 0 then
destination = stop.Stopovers[#stop.Stopovers] or destination
end
if isStationDestination(destination) then
local platform = "1"
if st.ScheduledStation and st.ScheduledStation.StationInfo and st.ScheduledStation.StationInfo.Platform then
platform = st.ScheduledStation.StationInfo.Platform
else
platform = State.stationPlatform or "1"
end
local cancelled = false
local reason = nil
local tr = stop.Train
if tr then
cancelled = tr.Cancelled == 1 or tr.Cancelled == true
local status = tr.Status
if type(status) == "table" then
for _, s in ipairs(status) do
if type(s) == "string" then
reason = mapTrainStatusReason(s) or reason
end
end
end
end
if cancelled then
reason = "cancelled"
local platform = "1"
if st.ScheduledStation and st.ScheduledStation.StationInfo and st.ScheduledStation.StationInfo.Platform then
platform = st.ScheduledStation.StationInfo.Platform
else
platform = State.stationPlatform or "1"
end
local scheduledArrival = pickEarlierTick(st.ScheduledArrival, st.ScheduledDeparture)
local realArrival = pickEarlierTick(st.RealTimeArrival, st.RealTimeDeparture)
local etaTicks = realArrival - currentTick
local etaSeconds = etaTicks / (State.tickRate or 20)
if etaSeconds < 0 then etaSeconds = 0 end
local announceKey = normalizeUpcomingKey(trainName, destination)
local etaLabel = buildEtaLabel(etaSeconds, etaTextByKey[announceKey])
local delayTicks = realArrival - scheduledArrival
local delayMinutes = math.floor(delayTicks / ((State.tickRate or 20) * 60))
if delayMinutes < 0 then delayMinutes = 0 end
local displayDelayMinutes = math.max(delayMinutes, tonumber(delayTextByKey[announceKey]) or 0)
if etaSeconds <= 60 and not State.announcedApproaching[announceKey] then
Utils.log("DEBUG", string.format("Train: %s, ETA: %.1fs, Ticks: %d, Rate: %.1f", trainName, etaSeconds, etaTicks, State.tickRate or 20))
end
table.insert(newUpcoming, {
local preCorrected = tryCorrectUpcomingEntryWithCurrentTrain({
trainName = trainName,
dest = destination,
platform = platform,
eta = etaSeconds,
etaLabel = etaLabel,
delay = displayDelayMinutes,
cancelled = cancelled,
reason = reason
platform = platform
})
local alertType = cancelled and "cancelled" or ((displayDelayMinutes > 0) and "delayed" or nil)
local alertKey = alertType and (announceKey .. "::" .. alertType) or nil
if alertKey then
seenAlertKeys[alertKey] = true
if isPendingAlertReady(alertKey, now) and not State.announcedAlerts[alertKey] then
State.announcedAlerts[alertKey] = now
Utils.log("INFO", string.format("Train Alert: %s (ETA: %ds, Delay: %dm, Cancelled: %s, Reason: %s)", trainName, etaSeconds, displayDelayMinutes, tostring(cancelled), tostring(reason or "")))
Network.send({
type = "train_alert",
stationName = State.stationName,
stationCode = State.stationCode,
stationPlatform = platform,
trainId = trainName,
destination = destination,
eta = tostring(etaSeconds) .. "s",
etaSeconds = etaSeconds,
delay = displayDelayMinutes,
cancelled = cancelled,
reason = reason
})
if preCorrected then
if not looksLikeValidDestinationText(trainName) and preCorrected.trainName then
trainName = preCorrected.trainName
end
if not looksLikeValidDestinationText(destination) and preCorrected.dest then
destination = preCorrected.dest
end
end
local isAtStation = checkIsAtStationWithCurrentTrain(trainName, destination, platform)
if not isAtStation and etaSeconds > 0 and etaSeconds <= 45 then
if not seenApproachKeys[announceKey] then
seenApproachKeys[announceKey] = true
local pending = State.pendingApproaching[announceKey]
if not pending then
State.pendingApproaching[announceKey] = { since = now, lastEta = etaSeconds }
else
if etaSeconds > (tonumber(pending.lastEta) or etaSeconds) + 15 then
pending.since = now
if isStationDestination(destination) then
local cancelled = false
local reason = nil
local tr = stop.Train
if tr then
cancelled = tr.Cancelled == 1 or tr.Cancelled == true
local status = tr.Status
if type(status) == "table" then
for _, s in ipairs(status) do
if type(s) == "string" then
reason = mapTrainStatusReason(s) or reason
end
end
pending.lastEta = etaSeconds
end
end
if cancelled then
reason = "cancelled"
end
local stableMs = tonumber(CONFIG.APPROACHING_STABLE_MS) or 15000
local currentPending = State.pendingApproaching[announceKey]
if currentPending and (now - (currentPending.since or now)) >= stableMs and not State.announcedApproaching[announceKey] then
State.announcedApproaching[announceKey] = now
Utils.log("INFO", string.format("Train Approaching: %s (ETA: %.1fs, Delay: %dm)", trainName, etaSeconds, delayMinutes))
local scheduledArrival = pickEarlierTick(st.ScheduledArrival, st.ScheduledDeparture)
local realArrival = pickEarlierTick(st.RealTimeArrival, st.RealTimeDeparture)
local etaTicks = realArrival - currentTick
local etaSeconds = etaTicks / (State.tickRate or 20)
if etaSeconds < 0 then etaSeconds = 0 end
local announceKey = normalizeUpcomingKey(trainName, destination)
local etaLabel = buildEtaLabel(etaSeconds, etaTextByKey[announceKey])
local delayTicks = realArrival - scheduledArrival
local delayMinutes = math.floor(delayTicks / ((State.tickRate or 20) * 60))
if delayMinutes < 0 then delayMinutes = 0 end
local displayDelayMinutes = math.max(delayMinutes, tonumber(delayTextByKey[announceKey]) or 0)
if etaSeconds <= 60 and not State.announcedApproaching[announceKey] then
Utils.log("DEBUG", string.format("Train: %s, ETA: %.1fs, Ticks: %d, Rate: %.1f", trainName, etaSeconds, etaTicks, State.tickRate or 20))
end
table.insert(newUpcoming, {
trainName = trainName,
dest = destination,
platform = platform,
eta = etaSeconds,
etaLabel = etaLabel,
delay = displayDelayMinutes,
cancelled = cancelled,
reason = reason
})
local alertType = cancelled and "cancelled" or ((displayDelayMinutes > 0) and "delayed" or nil)
local alertKey = alertType and (announceKey .. "::" .. alertType) or nil
if alertKey then
seenAlertKeys[alertKey] = true
if isPendingAlertReady(alertKey, now) and not State.announcedAlerts[alertKey] then
State.announcedAlerts[alertKey] = now
Utils.log("INFO", string.format("Train Alert: %s (ETA: %ds, Delay: %dm, Cancelled: %s, Reason: %s)", trainName, etaSeconds, displayDelayMinutes, tostring(cancelled), tostring(reason or "")))
Network.send({
type = "train_approaching",
type = "train_alert",
stationName = State.stationName,
stationCode = State.stationCode,
stationPlatform = platform,
trainId = trainName,
destination = destination,
eta = string.format("%.1fs", etaSeconds),
eta = tostring(etaSeconds) .. "s",
etaSeconds = etaSeconds,
delay = displayDelayMinutes,
cancelled = cancelled,
@@ -3320,7 +3300,42 @@ function Main.loop()
})
end
end
end
local isAtStation = checkIsAtStationWithCurrentTrain(trainName, destination, platform)
if not isAtStation and etaSeconds > 0 and etaSeconds <= 45 then
if not seenApproachKeys[announceKey] then
seenApproachKeys[announceKey] = true
local pending = State.pendingApproaching[announceKey]
if not pending then
State.pendingApproaching[announceKey] = { since = now, lastEta = etaSeconds }
else
if etaSeconds > (tonumber(pending.lastEta) or etaSeconds) + 15 then
pending.since = now
end
pending.lastEta = etaSeconds
end
local stableMs = tonumber(CONFIG.APPROACHING_STABLE_MS) or 15000
local currentPending = State.pendingApproaching[announceKey]
if currentPending and (now - (currentPending.since or now)) >= stableMs and not State.announcedApproaching[announceKey] then
State.announcedApproaching[announceKey] = now
Utils.log("INFO", string.format("Train Approaching: %s (ETA: %.1fs, Delay: %dm)", trainName, etaSeconds, delayMinutes))
Network.send({
type = "train_approaching",
stationName = State.stationName,
stationCode = State.stationCode,
stationPlatform = platform,
trainId = trainName,
destination = destination,
eta = string.format("%.1fs", etaSeconds),
etaSeconds = etaSeconds,
delay = displayDelayMinutes,
cancelled = cancelled,
reason = reason
})
end
end
end
end
end
end
@@ -3361,6 +3376,21 @@ function Main.loop()
if not tName then
platform, etaStr, tName, dest = string.match(cleanLine, "^([^%s]+)%s+(.-)%s+([^%s]+)%s+(.*)$")
end
local entryPlatform = platform or State.stationPlatform or "1"
local preCorrectedCT = tryCorrectUpcomingEntryWithCurrentTrain({
trainName = tName or "",
dest = dest or "",
platform = entryPlatform
})
if preCorrectedCT then
if tName and not looksLikeValidDestinationText(tName) and preCorrectedCT.trainName then
tName = preCorrectedCT.trainName
end
if dest and not looksLikeValidDestinationText(dest) and preCorrectedCT.dest then
dest = preCorrectedCT.dest
end
end
if tName and etaStr and isStationDestination(dest) then
local announceKey = normalizeUpcomingKey(tName, dest)