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 if type(stop.Stopovers) == "table" and #stop.Stopovers > 0 then
destination = stop.Stopovers[#stop.Stopovers] or destination destination = stop.Stopovers[#stop.Stopovers] or destination
end end
if isStationDestination(destination) then local platform = "1"
local platform = "1" if st.ScheduledStation and st.ScheduledStation.StationInfo and st.ScheduledStation.StationInfo.Platform then
if st.ScheduledStation and st.ScheduledStation.StationInfo and st.ScheduledStation.StationInfo.Platform then platform = st.ScheduledStation.StationInfo.Platform
platform = st.ScheduledStation.StationInfo.Platform else
else platform = State.stationPlatform or "1"
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"
end end
local scheduledArrival = pickEarlierTick(st.ScheduledArrival, st.ScheduledDeparture) local preCorrected = tryCorrectUpcomingEntryWithCurrentTrain({
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, trainName = trainName,
dest = destination, dest = destination,
platform = platform, platform = platform
eta = etaSeconds,
etaLabel = etaLabel,
delay = displayDelayMinutes,
cancelled = cancelled,
reason = reason
}) })
if preCorrected then
local alertType = cancelled and "cancelled" or ((displayDelayMinutes > 0) and "delayed" or nil) if not looksLikeValidDestinationText(trainName) and preCorrected.trainName then
local alertKey = alertType and (announceKey .. "::" .. alertType) or nil trainName = preCorrected.trainName
if alertKey then end
seenAlertKeys[alertKey] = true if not looksLikeValidDestinationText(destination) and preCorrected.dest then
if isPendingAlertReady(alertKey, now) and not State.announcedAlerts[alertKey] then destination = preCorrected.dest
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
})
end end
end end
local isAtStation = checkIsAtStationWithCurrentTrain(trainName, destination, platform) if isStationDestination(destination) then
if not isAtStation and etaSeconds > 0 and etaSeconds <= 45 then local cancelled = false
if not seenApproachKeys[announceKey] then local reason = nil
seenApproachKeys[announceKey] = true local tr = stop.Train
local pending = State.pendingApproaching[announceKey] if tr then
if not pending then cancelled = tr.Cancelled == 1 or tr.Cancelled == true
State.pendingApproaching[announceKey] = { since = now, lastEta = etaSeconds } local status = tr.Status
else if type(status) == "table" then
if etaSeconds > (tonumber(pending.lastEta) or etaSeconds) + 15 then for _, s in ipairs(status) do
pending.since = now if type(s) == "string" then
reason = mapTrainStatusReason(s) or reason
end
end end
pending.lastEta = etaSeconds
end end
end
if cancelled then
reason = "cancelled"
end
local stableMs = tonumber(CONFIG.APPROACHING_STABLE_MS) or 15000 local scheduledArrival = pickEarlierTick(st.ScheduledArrival, st.ScheduledDeparture)
local currentPending = State.pendingApproaching[announceKey] local realArrival = pickEarlierTick(st.RealTimeArrival, st.RealTimeDeparture)
if currentPending and (now - (currentPending.since or now)) >= stableMs and not State.announcedApproaching[announceKey] then local etaTicks = realArrival - currentTick
State.announcedApproaching[announceKey] = now local etaSeconds = etaTicks / (State.tickRate or 20)
Utils.log("INFO", string.format("Train Approaching: %s (ETA: %.1fs, Delay: %dm)", trainName, etaSeconds, delayMinutes)) 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({ Network.send({
type = "train_approaching", type = "train_alert",
stationName = State.stationName, stationName = State.stationName,
stationCode = State.stationCode, stationCode = State.stationCode,
stationPlatform = platform, stationPlatform = platform,
trainId = trainName, trainId = trainName,
destination = destination, destination = destination,
eta = string.format("%.1fs", etaSeconds), eta = tostring(etaSeconds) .. "s",
etaSeconds = etaSeconds, etaSeconds = etaSeconds,
delay = displayDelayMinutes, delay = displayDelayMinutes,
cancelled = cancelled, cancelled = cancelled,
@@ -3320,7 +3300,42 @@ function Main.loop()
}) })
end end
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 end
end end
@@ -3361,6 +3376,21 @@ function Main.loop()
if not tName then if not tName then
platform, etaStr, tName, dest = string.match(cleanLine, "^([^%s]+)%s+(.-)%s+([^%s]+)%s+(.*)$") platform, etaStr, tName, dest = string.match(cleanLine, "^([^%s]+)%s+(.-)%s+([^%s]+)%s+(.*)$")
end 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 if tName and etaStr and isStationDestination(dest) then
local announceKey = normalizeUpcomingKey(tName, dest) local announceKey = normalizeUpcomingKey(tName, dest)