feat(station): support manual identity for slave nodes
Allow station_broadcast to run without a train station peripheral on the back, add a local station_identity.cfg override for station name/code/platform, and display the active station code, platform, and identity mode so slave nodes can be grouped to a platform manually.
This commit is contained in:
+150
-15
@@ -2,7 +2,7 @@
|
||||
-- ============================================================================
|
||||
-- 1. Configuration & Constants
|
||||
-- ============================================================================
|
||||
local APP_VERSION_TEXT = "v1.0.6"
|
||||
local APP_VERSION_TEXT = "v1.0.7"
|
||||
|
||||
local CONFIG = {
|
||||
CLOUD_URL = "ws://stabadmin.fse-media.group",
|
||||
@@ -12,6 +12,7 @@ local CONFIG = {
|
||||
AUDIO_STATION = "Audio/Station/",
|
||||
DISK_AUDIO_BASE = "disk/Audio/",
|
||||
DISK_AUDIO_STATION = "disk/Audio/Station/",
|
||||
STATION_IDENTITY_FILE = "station_identity.cfg",
|
||||
AUDIO_VOLUME = 3,
|
||||
DEBUG = true
|
||||
}
|
||||
@@ -35,6 +36,8 @@ local State = {
|
||||
stationName = "Unknown",
|
||||
stationCode = "UNK",
|
||||
stationPlatform = "1",
|
||||
stationIdentityMode = "auto",
|
||||
hasStationPeripheral = false,
|
||||
registered = false,
|
||||
stationList = {},
|
||||
currentTrain = nil,
|
||||
@@ -54,8 +57,14 @@ local Peripherals = {}
|
||||
|
||||
function Peripherals.init()
|
||||
-- Station Peripheral
|
||||
Peripherals.station = peripheral.wrap("back", "train_station")
|
||||
or error("Train station peripheral required on 'back'")
|
||||
local back = peripheral.wrap("back")
|
||||
if back and peripheral.getType("back") == "train_station" then
|
||||
Peripherals.station = back
|
||||
State.hasStationPeripheral = true
|
||||
else
|
||||
Peripherals.station = nil
|
||||
State.hasStationPeripheral = false
|
||||
end
|
||||
-- Monitors
|
||||
local monitors = { peripheral.find("monitor") }
|
||||
if #monitors == 0 then error("At least one monitor is required") end
|
||||
@@ -84,6 +93,112 @@ function Utils.log(level, message)
|
||||
end
|
||||
end
|
||||
|
||||
function Utils.trim(value)
|
||||
return tostring(value or ""):match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
function Utils.writeStationIdentityTemplate()
|
||||
if fs.exists(CONFIG.STATION_IDENTITY_FILE) then return end
|
||||
local handle = fs.open(CONFIG.STATION_IDENTITY_FILE, "w")
|
||||
if not handle then return end
|
||||
handle.write(table.concat({
|
||||
"# Manual station identity override for slave nodes",
|
||||
"# Leave a value empty to keep auto detection when available",
|
||||
"station_name=",
|
||||
"station_code=",
|
||||
"station_platform=",
|
||||
""
|
||||
}, "\n"))
|
||||
handle.close()
|
||||
end
|
||||
|
||||
function Utils.loadStationIdentity()
|
||||
Utils.writeStationIdentityTemplate()
|
||||
if not fs.exists(CONFIG.STATION_IDENTITY_FILE) then
|
||||
return {}
|
||||
end
|
||||
|
||||
local handle = fs.open(CONFIG.STATION_IDENTITY_FILE, "r")
|
||||
if not handle then
|
||||
return {}
|
||||
end
|
||||
|
||||
local content = handle.readAll() or ""
|
||||
handle.close()
|
||||
|
||||
local data = {}
|
||||
for line in content:gmatch("[^\r\n]+") do
|
||||
local clean = Utils.trim(line)
|
||||
if clean ~= "" and not clean:match("^#") then
|
||||
local key, value = clean:match("^([%w_]+)%s*=%s*(.-)%s*$")
|
||||
if key then
|
||||
data[key:lower()] = Utils.trim(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
function Utils.resolveStationIdentity()
|
||||
local manual = Utils.loadStationIdentity()
|
||||
local physicalName = nil
|
||||
if Peripherals.station and type(Peripherals.station.getStationName) == "function" then
|
||||
physicalName = Utils.trim(Peripherals.station.getStationName() or "Unknown")
|
||||
if physicalName == "" then physicalName = nil end
|
||||
end
|
||||
|
||||
local stationCode = manual.station_code
|
||||
local stationPlatform = manual.station_platform
|
||||
local stationName = manual.station_name
|
||||
|
||||
if not stationName or stationName == "" then
|
||||
stationName = physicalName
|
||||
end
|
||||
if (not stationCode or stationCode == "") and stationName and stationName ~= "" then
|
||||
stationCode = Utils.getStationCode(stationName) or Utils.getBroadcastStationCode(stationName)
|
||||
end
|
||||
if not stationPlatform or stationPlatform == "" then
|
||||
stationPlatform = Utils.getPlatformNumber(stationName or physicalName, "1")
|
||||
end
|
||||
if not stationName or stationName == "" then
|
||||
if stationCode and stationCode ~= "" then
|
||||
stationName = string.upper(stationCode) .. " Platform " .. tostring(stationPlatform or "1")
|
||||
else
|
||||
stationName = "Manual Station"
|
||||
end
|
||||
end
|
||||
if not stationCode or stationCode == "" then
|
||||
stationCode = Utils.getStationCode(stationName) or Utils.getBroadcastStationCode(stationName)
|
||||
end
|
||||
|
||||
local hasManualIdentity =
|
||||
(manual.station_name and manual.station_name ~= "") or
|
||||
(manual.station_code and manual.station_code ~= "") or
|
||||
(manual.station_platform and manual.station_platform ~= "")
|
||||
|
||||
local identityMode = "auto"
|
||||
if hasManualIdentity then
|
||||
identityMode = "manual"
|
||||
elseif not Peripherals.station then
|
||||
identityMode = "manual-fallback"
|
||||
end
|
||||
|
||||
return {
|
||||
stationName = stationName,
|
||||
stationCode = stationCode or "UNK",
|
||||
stationPlatform = tostring(stationPlatform or "1"),
|
||||
mode = identityMode
|
||||
}
|
||||
end
|
||||
|
||||
function Utils.refreshStationIdentity()
|
||||
local identity = Utils.resolveStationIdentity()
|
||||
State.stationName = identity.stationName
|
||||
State.stationCode = identity.stationCode
|
||||
State.stationPlatform = identity.stationPlatform
|
||||
State.stationIdentityMode = identity.mode
|
||||
end
|
||||
|
||||
function Utils.getStationCode(name)
|
||||
if not name then return nil end
|
||||
name = tostring(name)
|
||||
@@ -1304,10 +1419,7 @@ function Network.send(data)
|
||||
end
|
||||
|
||||
function Network.register()
|
||||
State.stationName = (Peripherals.station.getStationName() or "Unknown"):match("^%s*(.-)%s*$") -- Trim
|
||||
-- Try to get a clean code from name, fallback to broadcast format
|
||||
State.stationCode = Utils.getStationCode(State.stationName) or Utils.getBroadcastStationCode(State.stationName)
|
||||
State.stationPlatform = Utils.getPlatformNumber(State.stationName, "1")
|
||||
Utils.refreshStationIdentity()
|
||||
|
||||
Network.send({
|
||||
type = "station_register",
|
||||
@@ -1493,6 +1605,7 @@ end
|
||||
function Network.heartbeat()
|
||||
while true do
|
||||
if State.ws then
|
||||
Utils.refreshStationIdentity()
|
||||
Network.send({
|
||||
type = "heartbeat",
|
||||
stationName = State.stationName,
|
||||
@@ -1518,6 +1631,8 @@ function Display.render()
|
||||
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 function classifyAudioSource(source, priority)
|
||||
@@ -1534,7 +1649,7 @@ function Display.render()
|
||||
return str
|
||||
end
|
||||
|
||||
mon.setCursorPos(1,3)
|
||||
mon.setCursorPos(1,4)
|
||||
if State.currentPlaying then
|
||||
local p = tonumber(State.currentPlaying.priority) or 0
|
||||
local kind = classifyAudioSource(State.currentPlaying.source, p)
|
||||
@@ -1556,19 +1671,19 @@ function Display.render()
|
||||
|
||||
-- Current Train
|
||||
if State.currentTrain then
|
||||
mon.setCursorPos(1,4)
|
||||
mon.setCursorPos(1,5)
|
||||
mon.setTextColor(COLORS.SUCCESS)
|
||||
mon.write("CURRENT TRAIN:")
|
||||
mon.setTextColor(COLORS.INFO)
|
||||
mon.setCursorPos(1,5)
|
||||
mon.write(State.currentTrain.id)
|
||||
mon.setCursorPos(1,6)
|
||||
mon.write(State.currentTrain.id)
|
||||
mon.setCursorPos(1,7)
|
||||
mon.write("Dest: " .. State.currentTrain.dest)
|
||||
|
||||
-- Countdown
|
||||
if State.currentTrain.departureTime then
|
||||
local remaining = math.max(0, State.currentTrain.departureTime - os.epoch("utc") / 1000)
|
||||
mon.setCursorPos(1, 8)
|
||||
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)))
|
||||
@@ -1578,9 +1693,13 @@ function Display.render()
|
||||
end
|
||||
end
|
||||
else
|
||||
mon.setCursorPos(1,4)
|
||||
mon.setCursorPos(1,5)
|
||||
mon.setTextColor(COLORS.WARNING)
|
||||
mon.write("NO TRAIN AT PLATFORM")
|
||||
if Peripherals.station then
|
||||
mon.write("NO TRAIN AT PLATFORM")
|
||||
else
|
||||
mon.write("SLAVE NODE MODE")
|
||||
end
|
||||
end
|
||||
|
||||
-- Logs
|
||||
@@ -1604,7 +1723,7 @@ function Display.renderTerminal()
|
||||
term.setBackgroundColor(colors.gray)
|
||||
term.setTextColor(colors.white)
|
||||
term.clearLine()
|
||||
local title = " Station Broadcast Console (" .. State.stationName .. " / " .. APP_VERSION_TEXT .. ") "
|
||||
local title = " Station Broadcast Console (" .. State.stationName .. " " .. tostring(State.stationCode) .. "/P" .. tostring(State.stationPlatform) .. " / " .. APP_VERSION_TEXT .. ") "
|
||||
term.setCursorPos(math.floor((w - #title)/2) + 1, 1)
|
||||
term.write(title)
|
||||
term.setBackgroundColor(colors.black)
|
||||
@@ -1711,6 +1830,14 @@ local Main = {}
|
||||
|
||||
function Main.loop()
|
||||
while true do
|
||||
if not Peripherals.station then
|
||||
State.currentTrain = nil
|
||||
Display.render()
|
||||
Display.renderTerminal()
|
||||
os.sleep(0.5)
|
||||
goto continue
|
||||
end
|
||||
|
||||
local hasTrain = Peripherals.station.isTrainPresent()
|
||||
|
||||
if hasTrain then
|
||||
@@ -1741,6 +1868,7 @@ function Main.loop()
|
||||
Display.render()
|
||||
Display.renderTerminal()
|
||||
os.sleep(0.5)
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1749,7 +1877,14 @@ end
|
||||
-- ============================================================================
|
||||
Peripherals.init()
|
||||
Audio.loadStationList()
|
||||
Utils.refreshStationIdentity()
|
||||
Utils.log("INFO", "StationBroadcast version " .. APP_VERSION_TEXT)
|
||||
if Peripherals.station then
|
||||
Utils.log("INFO", "Train station peripheral detected on back")
|
||||
else
|
||||
Utils.log("WARN", "No train station peripheral on back; using manual slave mode")
|
||||
Utils.log("INFO", "Edit " .. CONFIG.STATION_IDENTITY_FILE .. " to set station_name/station_code/station_platform")
|
||||
end
|
||||
|
||||
parallel.waitForAny(
|
||||
function()
|
||||
|
||||
Reference in New Issue
Block a user