feat: 从机站台吸附识别与时刻表自动同步 v1.0.73

- Lua端: 新增定期外设重扫描(PERIPHERAL_SCAN_INTERVAL=15s),支持热插拔站台方块
- Lua端: 新增定期身份同步,站台外设热插/热拔时自动刷新身份并重新register
- Lua端: 新增独立的schedule sync coroutine与station_schedule_sync消息类型(SCHEDULE_SYNC_INTERVAL=10s)
- Lua端: 扩展heartbeat携带hasStationPeripheral/stationPeripheralSide/hasBlockReader/hasCreateTarget/identityMode/upcomingTrains/currentTrain等字段
- 从机若吸附有站台方块(train_station外设),则自动进入主机逻辑: 识别到发、停靠列车检测,数据通过WS推送到服务器
- server端: 新增assignDeviceFields工具函数统一写入设备字段与时刻表
- server端: 新增handleScheduleSync处理station_schedule_sync消息,数据写入stations[name]并broadcast
- server端: 扩展handleHeartbeat与handleStationRegister,均调用assignDeviceFields存储设备状态和时刻表摘要
- server端: 扩展getStationGroups分组视图,新增hostCount/slaveCount/hasAnyStationPeripheral/upcomingTrains/currentTrain/members详细信息
- 时刻表合并: 同一站台组多节点的upcomingTrains按trainName@@dest@@platform去重合并; currentTrain优先主机数据
- 版本升级: v1.0.72 -> v1.0.73
This commit is contained in:
2026-08-16 13:33:10 +08:00
parent cd5b1d47b0
commit ae213a84a1
2 changed files with 269 additions and 15 deletions
+138 -3
View File
@@ -1,12 +1,14 @@
-- 1. Configuration & Constants
local APP_VERSION_TEXT = "v1.0.72"
local APP_VERSION_TEXT = "v1.0.73"
local CONFIG = {
CLOUD_URL = "ws://stabadmin.fse-media.group",
API_URL = "http://stabadmin.fse-media.group",
STARTUP_PATH = "/startup",
SYNC_INTERVAL = 30,
PERIPHERAL_SCAN_INTERVAL = 15,
SCHEDULE_SYNC_INTERVAL = 10,
AUDIO_BASE = "Audio/",
AUDIO_STATION = "Audio/Station/",
DISK_AUDIO_BASE = "disk/Audio/",
@@ -47,6 +49,7 @@ local State = {
registered = false,
stationList = {},
currentTrain = nil,
upcomingTrains = {},
logs = {},
logScrollOffset = 0,
audioRx = nil,
@@ -55,7 +58,10 @@ local State = {
currentPriority = -1,
interruptSeq = 0,
tickRate = 20,
updateInProgress = false
updateInProgress = false,
lastPeripheralScan = 0,
lastScheduleSync = 0,
lastIdentitySync = 0
}
@@ -2501,18 +2507,111 @@ function Network.heartbeat()
while true do
if State.ws then
Utils.refreshStationIdentity()
local hasBlockReader = Peripherals.blockReader ~= nil
local hasCreateTarget = Peripherals.createTarget ~= nil
local upcomingSummary = {}
if State.upcomingTrains and type(State.upcomingTrains) == "table" then
local maxN = math.min(#State.upcomingTrains, 5)
for i = 1, maxN do
local t = State.upcomingTrains[i]
if t then
table.insert(upcomingSummary, {
trainName = t.trainName,
dest = t.dest,
platform = t.platform,
eta = tonumber(t.eta) or 0,
etaLabel = t.etaLabel,
delay = tonumber(t.delay) or 0,
cancelled = not not t.cancelled,
reason = t.reason
})
end
end
end
local currentTrainInfo = nil
if State.currentTrain then
currentTrainInfo = {
id = State.currentTrain.id,
dest = State.currentTrain.dest,
nextStation = State.currentTrain.nextStation,
platform = State.currentTrain.platform,
departureTime = State.currentTrain.departureTime
}
end
Network.send({
type = "heartbeat",
stationName = State.stationName,
stationCode = State.stationCode,
stationPlatform = State.stationPlatform,
stationVersion = APP_VERSION_TEXT
stationVersion = APP_VERSION_TEXT,
hasStationPeripheral = State.hasStationPeripheral,
stationPeripheralSide = State.stationPeripheralSide,
hasBlockReader = hasBlockReader,
hasCreateTarget = hasCreateTarget,
identityMode = State.stationIdentityMode,
upcomingTrains = upcomingSummary,
currentTrain = currentTrainInfo,
upcomingCount = #(State.upcomingTrains or {})
})
end
os.sleep(10)
end
end
function Network.sendScheduleSync()
if not State.ws then return end
local upcomingTrainsPayload = {}
if State.upcomingTrains and type(State.upcomingTrains) == "table" then
for _, t in ipairs(State.upcomingTrains) do
table.insert(upcomingTrainsPayload, {
trainName = t.trainName,
dest = t.dest,
platform = t.platform,
eta = tonumber(t.eta) or 0,
etaLabel = t.etaLabel,
delay = tonumber(t.delay) or 0,
cancelled = not not t.cancelled,
reason = t.reason,
atStation = not not t._atStation,
departingIn = tonumber(t._departingIn) or nil
})
end
end
local currentTrainPayload = nil
if State.currentTrain then
local ct = State.currentTrain
currentTrainPayload = {
id = ct.id,
trainType = ct.trainType,
dest = ct.dest,
nextStation = ct.nextStation,
lineCode = ct.lineCode,
destCode = ct.destCode,
nextCode = ct.nextCode,
arrivalTime = ct.arrivalTime,
departureTime = ct.departureTime,
platform = ct.platform,
departureSent = not not ct.departureSent
}
end
Network.send({
type = "station_schedule_sync",
stationName = State.stationName,
stationCode = State.stationCode,
stationPlatform = State.stationPlatform,
hasStationPeripheral = State.hasStationPeripheral,
stationPeripheralSide = State.stationPeripheralSide,
hasBlockReader = Peripherals.blockReader ~= nil,
hasCreateTarget = Peripherals.createTarget ~= nil,
identityMode = State.stationIdentityMode,
upcomingTrains = upcomingTrainsPayload,
upcomingCount = #upcomingTrainsPayload,
currentTrain = currentTrainPayload,
tickRate = State.tickRate,
syncedAt = os.epoch("utc")
})
end
-- 7. Display System
@@ -3227,6 +3326,42 @@ end
function Main.loop()
while true do
local now = os.epoch("utc")
local scanInterval = tonumber(CONFIG.PERIPHERAL_SCAN_INTERVAL) or 15
if (now - State.lastPeripheralScan) >= scanInterval * 1000 then
State.lastPeripheralScan = now
local oldHasStation = State.hasStationPeripheral
local oldStationSide = State.stationPeripheralSide
pcall(function() Peripherals.init() end)
Utils.refreshStationIdentity()
if oldHasStation ~= State.hasStationPeripheral or oldStationSide ~= State.stationPeripheralSide then
if State.hasStationPeripheral then
Utils.log("INFO", "Station peripheral detected at runtime: " .. tostring(State.stationPeripheralSide or "unknown"))
pcall(function() Utils.syncStationIdentityFromPeripheral() end)
Utils.refreshStationIdentity()
if State.ws then pcall(function() Network.register() end) end
else
Utils.log("WARN", "Station peripheral removed at runtime")
end
end
end
local identityInterval = tonumber(CONFIG.PERIPHERAL_SCAN_INTERVAL) or 15
if State.hasStationPeripheral and (now - State.lastIdentitySync) >= identityInterval * 1000 then
State.lastIdentitySync = now
pcall(function()
if Utils.syncStationIdentityFromPeripheral() then
Utils.refreshStationIdentity()
end
end)
end
local scheduleInterval = tonumber(CONFIG.SCHEDULE_SYNC_INTERVAL) or 10
if (now - State.lastScheduleSync) >= scheduleInterval * 1000 then
State.lastScheduleSync = now
pcall(function() Network.sendScheduleSync() end)
end
local newUpcoming = {}
local etaTextByKey = {}
local delayTextByKey = {}