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:
+130
-11
@@ -408,7 +408,31 @@ function getStationGroups() {
|
|||||||
const key = getStationGroupKey(st);
|
const key = getStationGroupKey(st);
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
|
|
||||||
|
const memberTags = [];
|
||||||
|
if (st.hasStationPeripheral) memberTags.push('STATION');
|
||||||
|
if (st.hasBlockReader) memberTags.push('BLOCKREADER');
|
||||||
|
if (st.hasCreateTarget) memberTags.push('TARGET');
|
||||||
|
const isHost = Boolean(st.hasStationPeripheral);
|
||||||
|
|
||||||
|
const memberInfo = {
|
||||||
|
name: st.name,
|
||||||
|
code,
|
||||||
|
platform: st.platform || platform,
|
||||||
|
version: st.version || '',
|
||||||
|
lastSeen: st.lastSeen || 0,
|
||||||
|
hasStationPeripheral: isHost,
|
||||||
|
stationPeripheralSide: st.stationPeripheralSide || '',
|
||||||
|
hasBlockReader: Boolean(st.hasBlockReader),
|
||||||
|
hasCreateTarget: Boolean(st.hasCreateTarget),
|
||||||
|
identityMode: st.identityMode || '',
|
||||||
|
upcomingCount: Number(st.upcomingCount) || 0,
|
||||||
|
memberTags
|
||||||
|
};
|
||||||
|
|
||||||
if (!groups.has(key)) {
|
if (!groups.has(key)) {
|
||||||
|
const mergedTags = ['CC'];
|
||||||
|
if (isHost) mergedTags.push('HOST');
|
||||||
|
else mergedTags.push('SLAVE');
|
||||||
groups.set(key, {
|
groups.set(key, {
|
||||||
key,
|
key,
|
||||||
target: getStationGroupTarget(st),
|
target: getStationGroupTarget(st),
|
||||||
@@ -418,29 +442,72 @@ function getStationGroups() {
|
|||||||
lastSeen: st.lastSeen || 0,
|
lastSeen: st.lastSeen || 0,
|
||||||
version: st.version || '',
|
version: st.version || '',
|
||||||
source: 'cc',
|
source: 'cc',
|
||||||
tags: ['CC'],
|
tags: mergedTags,
|
||||||
memberNames: [st.name],
|
memberNames: [st.name],
|
||||||
memberCount: 1,
|
memberCount: 1,
|
||||||
primaryName: st.name
|
primaryName: st.name,
|
||||||
|
hasStationPeripheral: isHost,
|
||||||
|
hasBlockReader: Boolean(st.hasBlockReader),
|
||||||
|
hasCreateTarget: Boolean(st.hasCreateTarget),
|
||||||
|
hasAnyStationPeripheral: isHost,
|
||||||
|
hostCount: isHost ? 1 : 0,
|
||||||
|
slaveCount: isHost ? 0 : 1,
|
||||||
|
upcomingCount: Number(st.upcomingCount) || 0,
|
||||||
|
upcomingTrains: Array.isArray(st.upcomingTrains) ? st.upcomingTrains.slice() : [],
|
||||||
|
currentTrain: st.currentTrain || null,
|
||||||
|
members: [memberInfo]
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = groups.get(key);
|
const group = groups.get(key);
|
||||||
group.memberNames.push(st.name);
|
group.memberNames.push(st.name);
|
||||||
|
group.members.push(memberInfo);
|
||||||
group.memberCount = group.memberNames.length;
|
group.memberCount = group.memberNames.length;
|
||||||
|
if (isHost) group.hostCount = (group.hostCount || 0) + 1;
|
||||||
|
else group.slaveCount = (group.slaveCount || 0) + 1;
|
||||||
|
if (!group.hasAnyStationPeripheral && isHost) group.hasAnyStationPeripheral = true;
|
||||||
|
if (!group.hasBlockReader && st.hasBlockReader) group.hasBlockReader = true;
|
||||||
|
if (!group.hasCreateTarget && st.hasCreateTarget) group.hasCreateTarget = true;
|
||||||
|
if (!group.hasStationPeripheral && isHost) {
|
||||||
|
group.hasStationPeripheral = true;
|
||||||
|
group.primaryName = st.name;
|
||||||
|
group.name = st.name;
|
||||||
|
}
|
||||||
if ((st.lastSeen || 0) > (group.lastSeen || 0)) {
|
if ((st.lastSeen || 0) > (group.lastSeen || 0)) {
|
||||||
group.lastSeen = st.lastSeen || 0;
|
group.lastSeen = st.lastSeen || 0;
|
||||||
group.version = st.version || group.version || '';
|
group.version = st.version || group.version || '';
|
||||||
} else if (!group.version && st.version) {
|
} else if (!group.version && st.version) {
|
||||||
group.version = st.version;
|
group.version = st.version;
|
||||||
}
|
}
|
||||||
|
const existingKeys = new Set((group.upcomingTrains || []).map(t =>
|
||||||
|
String(t.trainName || '') + '@@' + String(t.dest || '') + '@@' + Number(t.platform || 0)
|
||||||
|
));
|
||||||
|
for (const t of (st.upcomingTrains || [])) {
|
||||||
|
const tk = String(t.trainName || '') + '@@' + String(t.dest || '') + '@@' + Number(t.platform || 0);
|
||||||
|
if (!existingKeys.has(tk)) {
|
||||||
|
if (!group.upcomingTrains) group.upcomingTrains = [];
|
||||||
|
group.upcomingTrains.push(t);
|
||||||
|
existingKeys.add(tk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.upcomingCount = (group.upcomingTrains || []).length;
|
||||||
|
if (!group.currentTrain && st.currentTrain) group.currentTrain = st.currentTrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(groups.values()).map(group => ({
|
return Array.from(groups.values()).map(group => {
|
||||||
|
const mergedTags = ['CC'];
|
||||||
|
if ((group.hostCount || 0) > 0) mergedTags.push(`HOST:${group.hostCount}`);
|
||||||
|
if ((group.slaveCount || 0) > 0) mergedTags.push(`SLAVE:${group.slaveCount}`);
|
||||||
|
if (group.memberCount > 1) mergedTags.push(`GROUP:${group.memberCount}`);
|
||||||
|
if (group.hasAnyStationPeripheral) mergedTags.push('HAS_STATION');
|
||||||
|
if (group.hasBlockReader) mergedTags.push('HAS_BLOCKREADER');
|
||||||
|
if (group.hasCreateTarget) mergedTags.push('HAS_TARGET');
|
||||||
|
return {
|
||||||
...group,
|
...group,
|
||||||
tags: group.memberCount > 1 ? ['CC', `GROUP:${group.memberCount}`] : ['CC']
|
tags: mergedTags
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveStationGroup(selector) {
|
function resolveStationGroup(selector) {
|
||||||
@@ -1857,6 +1924,9 @@ function handleMessage(ws, data) {
|
|||||||
case 'heartbeat':
|
case 'heartbeat':
|
||||||
handleHeartbeat(ws, data);
|
handleHeartbeat(ws, data);
|
||||||
break;
|
break;
|
||||||
|
case 'station_schedule_sync':
|
||||||
|
handleScheduleSync(ws, data);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log('WARNING', `Unknown message type: ${data.type}`);
|
log('WARNING', `Unknown message type: ${data.type}`);
|
||||||
}
|
}
|
||||||
@@ -1871,11 +1941,24 @@ function getCorrectStationName(name) {
|
|||||||
return cleanName;
|
return cleanName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assignDeviceFields(target, data) {
|
||||||
|
if (!target || !data) return;
|
||||||
|
if (data.hasStationPeripheral !== undefined) target.hasStationPeripheral = Boolean(data.hasStationPeripheral);
|
||||||
|
if (data.stationPeripheralSide !== undefined) target.stationPeripheralSide = String(data.stationPeripheralSide || '');
|
||||||
|
if (data.hasBlockReader !== undefined) target.hasBlockReader = Boolean(data.hasBlockReader);
|
||||||
|
if (data.hasCreateTarget !== undefined) target.hasCreateTarget = Boolean(data.hasCreateTarget);
|
||||||
|
if (data.identityMode !== undefined) target.identityMode = String(data.identityMode || '');
|
||||||
|
if (data.upcomingTrains !== undefined) target.upcomingTrains = Array.isArray(data.upcomingTrains) ? data.upcomingTrains : [];
|
||||||
|
if (data.upcomingCount !== undefined) target.upcomingCount = Number(data.upcomingCount) || 0;
|
||||||
|
if (data.currentTrain !== undefined) target.currentTrain = data.currentTrain || null;
|
||||||
|
if (data.tickRate !== undefined) target.tickRate = Number(data.tickRate) || 20;
|
||||||
|
if (data.syncedAt !== undefined) target.syncedAt = Number(data.syncedAt) || Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
function handleHeartbeat(ws, data) {
|
function handleHeartbeat(ws, data) {
|
||||||
if (data.stationName) {
|
if (data.stationName) {
|
||||||
const cleanName = data.stationName.trim();
|
const cleanName = data.stationName.trim();
|
||||||
if (!stations[cleanName]) {
|
if (!stations[cleanName]) {
|
||||||
// Auto-register if missing (e.g. server restarted)
|
|
||||||
stations[cleanName] = {
|
stations[cleanName] = {
|
||||||
name: cleanName,
|
name: cleanName,
|
||||||
code: data.stationCode || '',
|
code: data.stationCode || '',
|
||||||
@@ -1884,8 +1967,13 @@ function handleHeartbeat(ws, data) {
|
|||||||
lastSeen: Date.now(),
|
lastSeen: Date.now(),
|
||||||
ws: ws
|
ws: ws
|
||||||
};
|
};
|
||||||
|
assignDeviceFields(stations[cleanName], data);
|
||||||
normalizeStationRecord(stations[cleanName]);
|
normalizeStationRecord(stations[cleanName]);
|
||||||
log('INFO', `Station auto-registered via heartbeat: "${cleanName}"${data.stationVersion ? ` ${data.stationVersion}` : ''}`);
|
const tags = [];
|
||||||
|
if (stations[cleanName].hasStationPeripheral) tags.push('HAS_STATION');
|
||||||
|
if (stations[cleanName].hasBlockReader) tags.push('HAS_BLOCKREADER');
|
||||||
|
if (stations[cleanName].hasCreateTarget) tags.push('HAS_TARGET');
|
||||||
|
log('INFO', `Station auto-registered via heartbeat: "${cleanName}"${data.stationVersion ? ` ${data.stationVersion}` : ''}${tags.length ? ' [' + tags.join(',') + ']' : ''}`);
|
||||||
maybeSendStationUpdate(ws, cleanName, data.stationVersion || stations[cleanName].version);
|
maybeSendStationUpdate(ws, cleanName, data.stationVersion || stations[cleanName].version);
|
||||||
broadcastUpdate();
|
broadcastUpdate();
|
||||||
} else {
|
} else {
|
||||||
@@ -1893,10 +1981,14 @@ function handleHeartbeat(ws, data) {
|
|||||||
if (data.stationCode) stations[cleanName].code = data.stationCode;
|
if (data.stationCode) stations[cleanName].code = data.stationCode;
|
||||||
if (data.stationPlatform) stations[cleanName].platform = data.stationPlatform;
|
if (data.stationPlatform) stations[cleanName].platform = data.stationPlatform;
|
||||||
if (data.stationVersion) stations[cleanName].version = data.stationVersion;
|
if (data.stationVersion) stations[cleanName].version = data.stationVersion;
|
||||||
if (stations[cleanName].ws !== ws) {
|
const oldWS = stations[cleanName].ws;
|
||||||
stations[cleanName].ws = ws; // Update WS in case of reconnect
|
assignDeviceFields(stations[cleanName], data);
|
||||||
|
if (oldWS !== ws) {
|
||||||
|
stations[cleanName].ws = ws;
|
||||||
log('INFO', `Station WebSocket updated via heartbeat: "${cleanName}"${data.stationVersion ? ` ${data.stationVersion}` : ''}`);
|
log('INFO', `Station WebSocket updated via heartbeat: "${cleanName}"${data.stationVersion ? ` ${data.stationVersion}` : ''}`);
|
||||||
broadcastUpdate();
|
broadcastUpdate();
|
||||||
|
} else if (data.upcomingTrains || data.currentTrain || data.hasStationPeripheral !== undefined) {
|
||||||
|
broadcastUpdate();
|
||||||
}
|
}
|
||||||
normalizeStationRecord(stations[cleanName]);
|
normalizeStationRecord(stations[cleanName]);
|
||||||
maybeSendStationUpdate(ws, cleanName, data.stationVersion || stations[cleanName].version);
|
maybeSendStationUpdate(ws, cleanName, data.stationVersion || stations[cleanName].version);
|
||||||
@@ -1904,6 +1996,33 @@ function handleHeartbeat(ws, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleScheduleSync(ws, data) {
|
||||||
|
if (!data.stationName) return;
|
||||||
|
const cleanName = String(data.stationName || '').trim();
|
||||||
|
if (!stations[cleanName]) {
|
||||||
|
stations[cleanName] = {
|
||||||
|
name: cleanName,
|
||||||
|
code: data.stationCode || '',
|
||||||
|
platform: data.stationPlatform || getPlatformNumberFromName(cleanName),
|
||||||
|
version: '',
|
||||||
|
lastSeen: Date.now(),
|
||||||
|
ws: ws
|
||||||
|
};
|
||||||
|
assignDeviceFields(stations[cleanName], data);
|
||||||
|
normalizeStationRecord(stations[cleanName]);
|
||||||
|
log('INFO', `Station auto-registered via schedule sync: "${cleanName}" (upcoming: ${Number(data.upcomingCount) || 0})`);
|
||||||
|
broadcastUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stations[cleanName].lastSeen = Date.now();
|
||||||
|
if (data.stationCode) stations[cleanName].code = data.stationCode;
|
||||||
|
if (data.stationPlatform) stations[cleanName].platform = data.stationPlatform;
|
||||||
|
if (stations[cleanName].ws !== ws) stations[cleanName].ws = ws;
|
||||||
|
assignDeviceFields(stations[cleanName], data);
|
||||||
|
normalizeStationRecord(stations[cleanName]);
|
||||||
|
broadcastUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
function handleStationRegister(ws, data) {
|
function handleStationRegister(ws, data) {
|
||||||
const { stationName, stationCode, stationPlatform, stationVersion } = data;
|
const { stationName, stationCode, stationPlatform, stationVersion } = data;
|
||||||
const cleanName = stationName.trim();
|
const cleanName = stationName.trim();
|
||||||
@@ -1914,13 +2033,13 @@ function handleStationRegister(ws, data) {
|
|||||||
platform: stationPlatform || getPlatformNumberFromName(cleanName),
|
platform: stationPlatform || getPlatformNumberFromName(cleanName),
|
||||||
version: stationVersion || '',
|
version: stationVersion || '',
|
||||||
lastSeen: Date.now(),
|
lastSeen: Date.now(),
|
||||||
ws: ws // Keep reference if we need to send back
|
ws: ws
|
||||||
};
|
};
|
||||||
|
assignDeviceFields(stations[cleanName], data);
|
||||||
normalizeStationRecord(stations[cleanName]);
|
normalizeStationRecord(stations[cleanName]);
|
||||||
|
|
||||||
log('SUCCESS', `Station registered: "${cleanName}" (${stationCode})${stationVersion ? ` ${stationVersion}` : ''}`);
|
log('SUCCESS', `Station registered: "${cleanName}" (${stationCode})${stationVersion ? ` ${stationVersion}` : ''}`);
|
||||||
|
|
||||||
// Send ack
|
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'station_register_ack',
|
type: 'station_register_ack',
|
||||||
stationName: cleanName
|
stationName: cleanName
|
||||||
|
|||||||
+138
-3
@@ -1,12 +1,14 @@
|
|||||||
|
|
||||||
-- 1. Configuration & Constants
|
-- 1. Configuration & Constants
|
||||||
local APP_VERSION_TEXT = "v1.0.72"
|
local APP_VERSION_TEXT = "v1.0.73"
|
||||||
|
|
||||||
local CONFIG = {
|
local CONFIG = {
|
||||||
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
||||||
API_URL = "http://stabadmin.fse-media.group",
|
API_URL = "http://stabadmin.fse-media.group",
|
||||||
STARTUP_PATH = "/startup",
|
STARTUP_PATH = "/startup",
|
||||||
SYNC_INTERVAL = 30,
|
SYNC_INTERVAL = 30,
|
||||||
|
PERIPHERAL_SCAN_INTERVAL = 15,
|
||||||
|
SCHEDULE_SYNC_INTERVAL = 10,
|
||||||
AUDIO_BASE = "Audio/",
|
AUDIO_BASE = "Audio/",
|
||||||
AUDIO_STATION = "Audio/Station/",
|
AUDIO_STATION = "Audio/Station/",
|
||||||
DISK_AUDIO_BASE = "disk/Audio/",
|
DISK_AUDIO_BASE = "disk/Audio/",
|
||||||
@@ -47,6 +49,7 @@ local State = {
|
|||||||
registered = false,
|
registered = false,
|
||||||
stationList = {},
|
stationList = {},
|
||||||
currentTrain = nil,
|
currentTrain = nil,
|
||||||
|
upcomingTrains = {},
|
||||||
logs = {},
|
logs = {},
|
||||||
logScrollOffset = 0,
|
logScrollOffset = 0,
|
||||||
audioRx = nil,
|
audioRx = nil,
|
||||||
@@ -55,7 +58,10 @@ local State = {
|
|||||||
currentPriority = -1,
|
currentPriority = -1,
|
||||||
interruptSeq = 0,
|
interruptSeq = 0,
|
||||||
tickRate = 20,
|
tickRate = 20,
|
||||||
updateInProgress = false
|
updateInProgress = false,
|
||||||
|
lastPeripheralScan = 0,
|
||||||
|
lastScheduleSync = 0,
|
||||||
|
lastIdentitySync = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2501,18 +2507,111 @@ function Network.heartbeat()
|
|||||||
while true do
|
while true do
|
||||||
if State.ws then
|
if State.ws then
|
||||||
Utils.refreshStationIdentity()
|
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({
|
Network.send({
|
||||||
type = "heartbeat",
|
type = "heartbeat",
|
||||||
stationName = State.stationName,
|
stationName = State.stationName,
|
||||||
stationCode = State.stationCode,
|
stationCode = State.stationCode,
|
||||||
stationPlatform = State.stationPlatform,
|
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
|
end
|
||||||
os.sleep(10)
|
os.sleep(10)
|
||||||
end
|
end
|
||||||
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
|
-- 7. Display System
|
||||||
|
|
||||||
@@ -3227,6 +3326,42 @@ end
|
|||||||
function Main.loop()
|
function Main.loop()
|
||||||
while true do
|
while true do
|
||||||
local now = os.epoch("utc")
|
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 newUpcoming = {}
|
||||||
local etaTextByKey = {}
|
local etaTextByKey = {}
|
||||||
local delayTextByKey = {}
|
local delayTextByKey = {}
|
||||||
|
|||||||
Reference in New Issue
Block a user