diff --git a/gate.lua b/gate.lua index 0a847d2..2bdd9a8 100644 --- a/gate.lua +++ b/gate.lua @@ -2,7 +2,7 @@ local DEFAULT_SERVER_BASE = "http://ticket.fse-media.group" local DEFAULT_SERVER_PATH = "/api/tickets/check" local GATE_OPEN_SECONDS = 2 local VERSION = "v1.5.8" -local VERSION_CHECK_INTERVAL = 60 +local VERSION_CHECK_INTERVAL = 5 local CONFIG_PATH = "gate_config.json" @@ -158,7 +158,7 @@ end local function draw(statusLine1, statusLine2, statusColor) clear() local w, h = termDev.getSize() - centerText(1, "GATE", colors.cyan) + centerText(2, "GATE", colors.cyan) drawVersionIndicator(w) drawServerStatusIndicator(w) if statusLine1 and #statusLine1 > 0 then @@ -1352,6 +1352,21 @@ while true do pcall(function() refreshRemoteLuaVersion(guessBaseFromStatusURL(serverURL)) end) + if versionMismatch == true then + pcall(function() + clear() + local w, h = termDev.getSize() + centerText(math.floor(h / 2), "Auto Updating...", colors.yellow) + end) + pcall(function() + if shell and type(shell.run) == "function" then + shell.run("update_gate.lua") + else + os.run(getfenv and getfenv() or _ENV, "update_gate.lua") + end + end) + os.reboot() + end drawReadyScreen() versionTimer = os.startTimer(VERSION_CHECK_INTERVAL) end diff --git a/ticketmachine.lua b/ticketmachine.lua index 2979121..0694c3c 100644 --- a/ticketmachine.lua +++ b/ticketmachine.lua @@ -251,7 +251,7 @@ local function normalizeTrainTypeLabel(v) return 'Express' end if s == 'single' then - return 'single' + return 'Single' end return 'Local' end @@ -657,6 +657,20 @@ local function backgroundSyncTask() while true do sleep(5) pcall(refreshConfigOnce) + if versionMismatch == true then + pcall(function() + clear() + centerText(math.floor(h/2), "Auto Updating...", colors.yellow) + end) + pcall(function() + if shell and type(shell.run) == "function" then + shell.run("update_machine.lua") + else + os.run(getfenv and getfenv() or _ENV, "update_machine.lua") + end + end) + os.reboot() + end end end @@ -1075,6 +1089,7 @@ end local function stationDisplay(code) code = normalizeCode(code) if #code == 0 then return '' end + if code == 'SINGLE_SEGMENT' then return 'Any 1 Segment' end local st = stationByCode[code] if st then return tostring(st.en_name or st.name or code) .. ' ' .. code end return code @@ -1153,43 +1168,152 @@ local function addCancelButton() addButton(2, h, 'CANCEL', 8, 1, {colors.red, colors.white}, function() ui_cancel_request = true end) end +local singleSegments = nil +local singleSegmentScroll = 0 + +local function buildSingleSegments() + local segments = {} + local fares = CFG.fares or {} + for _, line in ipairs(CFG.lines or {}) do + local stops = line.stops or line['站点列表'] or {} + local lineFares = {} + for i = 1, #stops - 1 do + local a, b = stops[i], stops[i+1] + local f = nil + for _, fx in ipairs(fares) do + if (fx.from == a and fx.to == b) or (fx.from == b and fx.to == a) then + f = fx + break + end + end + if f then + local cost = tonumber(f.cost_regular or f.cost) or 0 + lineFares[cost] = true + end + end + for cost, _ in pairs(lineFares) do + table.insert(segments, { + lineName = line.name or line['线路名称'] or 'Unknown', + color = line.color or line['颜色'], + cost = cost + }) + end + end + return segments +end + +local function showSingleSegmentSelection() + if not singleSegments then + singleSegments = buildSingleSegments() + end + while state.page == 'single_segment' do + drawHeader('Select Single Segment', 'Select a line and fare') + Buttons = {} + local itemsPerPage = math.floor((h - 10) / 4) * 2 + if itemsPerPage < 2 then itemsPerPage = 2 end + local maxScroll = math.ceil(#singleSegments / itemsPerPage) - 1 + if maxScroll < 0 then maxScroll = 0 end + if singleSegmentScroll > maxScroll then singleSegmentScroll = maxScroll end + if singleSegmentScroll < 0 then singleSegmentScroll = 0 end + + local startIdx = singleSegmentScroll * itemsPerPage + 1 + local endIdx = math.min(startIdx + itemsPerPage - 1, #singleSegments) + + local bw = math.floor((w - 6) / 2) + local bx1, bx2 = 2, w - bw - 1 + local by = 5 + for i = startIdx, endIdx do + local seg = singleSegments[i] + local colIdx = (i - startIdx) % 2 + local rowIdx = math.floor((i - startIdx) / 2) + local x = colIdx == 0 and bx1 or bx2 + local y = by + rowIdx * 4 + + local label = seg.lineName .. ' $' .. tostring(seg.cost) + if #label > bw then label = label:sub(1, bw) end + local isSelected = (state.singleSegment == seg) + addButton(x, y, label, bw, 3, {isSelected and colors.green or colors.blue, colors.white}, function() + state.singleSegment = seg + end) + end + + if singleSegmentScroll > 0 then + addButton(w-8, 2, 'UP', 6, 1, {colors.gray, colors.white}, function() singleSegmentScroll = singleSegmentScroll - 1 end) + end + if singleSegmentScroll < maxScroll then + addButton(w-8, h-5, 'DN', 6, 1, {colors.gray, colors.white}, function() singleSegmentScroll = singleSegmentScroll + 1 end) + end + + addCancelButton() + addButton(2, h-3, '<-back', 8, 3, {colors.black, colors.red}, function() state.page = 'home' end) + if state.singleSegment then + addButton(w-9, h-3, 'NEXT->', 8, 3, {colors.black, colors.green}, function() + state.departure = state.singleSegment.lineName + state.terminal = 'SINGLE_SEGMENT' + state.trainType = 'single' + state.trips = 1 + state.cost = state.singleSegment.cost + state.page = 'order' + end) + end + waitButtons() + if ui_cancel_request then + renderConfirmCancel() + waitButtons() + if ui_cancel_confirmed then stopAudio(); state.page = 'home'; ui_cancel_confirmed = false end + ui_cancel_request = false + end + end +end + local function showHome() state.stationName = (CFG.current_station and (CFG.current_station.en_name or CFG.current_station.name)) or 'Station' state.stationCode = (CFG.current_station and CFG.current_station.code) or CURRENT_STATION_CODE drawHeader('FSE Ticket Machine', 'Select Mode', true) Buttons = {} local btnW, btnH = 12, 3 - local y = math.floor(h/2) - 2 + local y = math.floor(h/2) - 3 local x1, x2, x3 = math.floor(w/2) - btnW - 2, math.floor(w/2) + 3, math.floor(w/2) - math.floor(btnW / 2) if x1 < 2 or (x2 + btnW) > w-1 then local cx = math.floor((w - btnW) / 2) + 1 - addButton(cx, y-2, 'NEW', btnW, btnH, {colors.green, colors.white}, function() + addButton(cx, y-4, 'NEW', btnW, btnH, {colors.green, colors.white}, function() resetTicketFlow() stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete() state.page = 'departure' end) - addButton(cx, y+2, 'CARD', btnW, btnH, {colors.orange, colors.white}, function() + addButton(cx, y, 'SINGLE', btnW, btnH, {colors.blue, colors.white}, function() + resetTicketFlow() + stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete() + state.page = 'single_segment' + end) + addButton(cx, y+4, 'CARD', btnW, btnH, {colors.orange, colors.white}, function() resetCardFlow('open') stopAudio(); state.page = 'card_home' end) - addButton(cx, y+6, 'ONLINE', btnW, btnH, {colors.cyan, colors.white}, function() + addButton(cx, y+8, 'ONLINE', btnW, btnH, {colors.cyan, colors.white}, function() resetTicketFlow() stopAudio(); state.page = 'online' end) else - addButton(x1, y, 'NEW', btnW, btnH, {colors.green, colors.white}, function() + local cx = math.floor((w - btnW) / 2) + 1 + addButton(x1, y-2, 'NEW', btnW, btnH, {colors.green, colors.white}, function() resetTicketFlow() stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete() state.page = 'departure' end) - addButton(x2, y, 'ONLINE', btnW, btnH, {colors.cyan, colors.white}, function() + addButton(x2, y-2, 'SINGLE', btnW, btnH, {colors.blue, colors.white}, function() resetTicketFlow() - stopAudio(); state.page = 'online' + stopAudio(); playAudioFile('Audio/welcome.wav'); waitAudioComplete() + state.page = 'single_segment' end) - addButton(x3, y+4, 'CARD', btnW, btnH, {colors.orange, colors.white}, function() + addButton(x1, y+2, 'CARD', btnW, btnH, {colors.orange, colors.white}, function() resetCardFlow('open') stopAudio(); state.page = 'card_home' end) + addButton(x2, y+2, 'ONLINE', btnW, btnH, {colors.cyan, colors.white}, function() + resetTicketFlow() + stopAudio(); state.page = 'online' + end) end waitButtons() end @@ -1692,8 +1816,8 @@ local function drawOrder() local cardSub = (state.cardMode == 'redeem') and 'Redeem IC card order' or 'Open new stored-value card' drawHeader('Confirm Card', cardSub) else - local depLine = stationDisplay(state.departure) - local terLine = stationDisplay(state.terminal) + local depLine = (state.trainType == 'single') and state.departure or stationDisplay(state.departure) + local terLine = (state.trainType == 'single') and 'Any 1 Segment' or stationDisplay(state.terminal) drawHeader('Confirm Order', 'From: ' .. depLine .. ' To: ' .. terLine) end local y = 5 @@ -1726,8 +1850,10 @@ local function drawOrder() L('Type', state.trainType or 'N/A', colors.lightBlue) local rides = tonumber(state.trips) or 1 if cost == nil then - local unit = computeCost(state.departure or '', state.terminal or '', state.trainType or 'Local') - cost = unit and (unit * rides) or nil + if state.trainType ~= 'single' then + local unit = computeCost(state.departure or '', state.terminal or '', state.trainType or 'Local') + cost = unit and (unit * rides) or nil + end end L('Trips', rides) L('Cost', cost, colors.red) @@ -1784,18 +1910,23 @@ local function showOrderAndAudio() state.cost = 0 end elseif not state.voucher_code then - local unit = computeCost(state.departure or '', state.terminal or '', state.trainType or 'Local') - if not unit then - showAlert('No route') - state.page = 'terminal' - return + if state.trainType == 'single' then + -- Cost is already set in showSingleSegmentSelection + if state.cost == nil then state.cost = 0 end + else + local unit = computeCost(state.departure or '', state.terminal or '', state.trainType or 'Local') + if not unit then + showAlert('No route') + state.page = 'terminal' + return + end + state.cost = unit * (state.trips or 1) end - state.cost = unit * (state.trips or 1) else if state.cost == nil then state.cost = 0 end end - if state.productMode ~= 'card' and sameLogicalStation(state.departure, state.terminal) then + if state.productMode ~= 'card' and state.trainType ~= 'single' and sameLogicalStation(state.departure, state.terminal) then showAlert('Same station') state.page = state.voucher_code and 'online' or 'terminal' return @@ -2324,6 +2455,7 @@ end local function mainPageLoop() while true do if state.page == 'home' then showHome() + elseif state.page == 'single_segment' then showSingleSegmentSelection() elseif state.page == 'card_home' then showCardHome() elseif state.page == 'card_name' then showCardNameInput() elseif state.page == 'card_topup' then showCardTopup()