-- Airlock control v2.0 -- Физическая раскладка шлюза: -- Слева — большая внешняя дверь (наружу, под воду) -- Справа (изнутри) — стеклянная внутренняя дверь (внутрь станции) -- Кнопки внутри шлюза (слева направо): -- Пин 3 = левая кнопка = внутренняя (вход 4) -- Пин 4 = средняя кнопка = правая внутри (вход 3) -- Пин 5 = наружная правая кнопка снаружи (вход 5) -- === Состояния === -- IDLE — вода ~0%, обе двери закрыты. Ожидание команды. -- FLOODING — насос качает воду внутрь (подготовка к выходу). "Затопление" -- READY_OUT — вода ≥95%, внутр.дверь закрыта, внешн. закрыта. "Готов вых" -- READY_IN — вода ≥95%, внутр.дверь закрыта, внешн. ОТКРЫТА. "Готов Вх" -- DRAINING — насос откачивает воду (после входа снаружи). "Осушение" -- FAULT — нештатная ситуация. "Неисправен" -- === Входы === -- 1 — левая дверь (внешняя): 0=закрыта, 1=открыта -- 2 — правая дверь (внутренняя): 0=закрыта, 1=открыта -- 3 — правая кнопка (внутри, пин 4) -- 4 — внутренняя кнопка (внутри, левая, пин 3) -- 5 — наружная правая кнопка (снаружи, пин 5) -- 6 — уровень воды в шлюзе (-100..100) -- === Выходы === -- 1 — внешняя дверь: 1=открыть, 0=закрыть -- 2 — внутренняя дверь: 1=открыть, 0=закрыть -- 3 — насос: 1=вкл, 0=выкл -- 4 — целевой уровень воды (-100 сухо, 100 заполнен) -- 5 — текст статуса -- 6 — цвет текста (rrr,ggg,bbb) inp = {} --out = { --[1] = 0, --[2] = 0, --[3] = 0, --[4] = 0, --[5] = "", --[6] = "100,180,100", --} -- Константы задержек (сек) local MIN_FLOOD_TIME = 4.0 local MIN_DRAIN_TIME = 4.0 local INTERNAL_DOOR_DELAY = 4.0 local FAULT_HYSTERESIS = 2.0 local S = { IDLE = 0, FLOODING = 1, READY_OUT = 2, READY_IN = 3, DRAINING = 4, FAULT = 5, } local state = S.IDLE local stateTimer = 0.0 local doorTimer = 0.0 local openIntDoor= false -- флаг: открыть внутреннюю дверь (пропустить человека) -- Детекция фронта кнопок local prevBtn = { [3] = 0, [4] = 0, [5] = 0 } local btnEdge = { [3] = 0, [4] = 0, [5] = 0 } local STATUS_TEXT = { [S.IDLE] = "", [S.FLOODING] = "Затопление", [S.READY_OUT] = "Готов вых", [S.READY_IN] = "Готов Вх", [S.DRAINING] = "Осушение", [S.FAULT] = "Неисправен", } local STATUS_COLOR = { [S.IDLE] = "100,180,100", [S.FLOODING] = "255,165,0", [S.READY_OUT] = "0,200,50", [S.READY_IN] = "0,200,200", [S.DRAINING] = "0,100,255", [S.FAULT] = "200,0,0", } local function setOutputs(d1, d2, pump, target) out[1] = d1 and 1 or 0 out[2] = d2 and 1 or 0 out[3] = pump and 1 or 0 out[4] = target or 0 end local function setStatus(s) out[5] = STATUS_TEXT[s] or "" out[6] = STATUS_COLOR[s] or STATUS_COLOR[S.FAULT] end local function gotoState(s) if state == s then return end state = s stateTimer = 0.0 end -- Аварийные условия local function checkFault(water, leftDoor, rightDoor) if rightDoor == 1 and water > 10 then return true end if leftDoor == 1 and rightDoor == 1 then return true end if leftDoor == 1 and water < 50 then return true end return false end function upd(dt) -- Чтение входов local leftDoor = inp[1] or 0 local rightDoor = inp[2] or 0 local btnRight = inp[3] or 0 local btnInternal= inp[4] or 0 local btnExternal= inp[5] or 0 local water = inp[6] or 0 -- Детекция фронтов for _, p in ipairs({3,4,5}) do local v = inp[p] or 0 btnEdge[p] = (v == 1 and prevBtn[p] == 0) and 1 or 0 prevBtn[p] = v end stateTimer = stateTimer + dt -- === Аварийный контроль (высший приоритет) === if checkFault(water, leftDoor, rightDoor) then gotoState(S.FAULT) end if state == S.FAULT and not checkFault(water, leftDoor, rightDoor) and stateTimer > FAULT_HYSTERESIS then gotoState(S.IDLE) end -- === Логика состояний === -- IDLE if state == S.IDLE then -- Если дверь открывали для пропуска — закрыть по таймеру if openIntDoor then doorTimer = doorTimer + dt openIntDoor = doorTimer < INTERNAL_DOOR_DELAY end if btnEdge[4] == 1 then -- Внутренняя кнопка → подготовка к выходу (затопление) setOutputs(false, false, true, 100) gotoState(S.FLOODING) elseif btnEdge[5] == 1 then -- Наружная кнопка → открыть внешнюю дверь, впустить воду setOutputs(true, false, false, 0) gotoState(S.FLOODING) elseif leftDoor == 1 then -- Внешнюю открыли вручную — подтверждаем и начинаем заполнение out[1] = 1 gotoState(S.FLOODING) else -- Ручное управление внутренней дверью (для пропуска) setOutputs(false, openIntDoor, false, 0) setStatus(S.IDLE) end -- FLOODING -- Внешнюю дверь НЕ трогаем — она уже установлена при входе в состояние: -- закрыта (выход — закачка насосом) или открыта (вход — естественное заполнение) elseif state == S.FLOODING then out[2] = 0 out[3] = 1 out[4] = 100 -- Если дверь открыли вручную — подтверждаем, иначе привод будет бороться if leftDoor == 1 then out[1] = 1 end setStatus(S.FLOODING) if btnEdge[3] == 1 then -- Правая кнопка → отмена, сброс setOutputs(false, false, false, 0) gotoState(S.IDLE) end if water >= 95 and stateTimer >= MIN_FLOOD_TIME then if leftDoor == 1 then -- Внешняя открыта, вода есть → готовность к входу gotoState(S.READY_IN) else -- Внешняя закрыта, вода закачана насосом → готовность к выходу gotoState(S.READY_OUT) end end -- READY_OUT -- Не перезаписываем out[1] — внешняя дверь открывается однократно по кнопке elseif state == S.READY_OUT then out[2] = 0 out[3] = 0 out[4] = 100 setStatus(S.READY_OUT) if btnEdge[3] == 1 then out[1] = 1 -- открыть внешнюю дверь (выход) end if leftDoor == 1 then gotoState(S.READY_IN) end if btnEdge[4] == 1 then out[1] = 0 out[3] = 1 out[4] = -100 gotoState(S.DRAINING) end -- READY_IN elseif state == S.READY_IN then setOutputs(true, false, false, 100) setStatus(S.READY_IN) if leftDoor == 0 then -- Внешняя закрыта (человек зашёл) → осушение setOutputs(false, false, true, -100) gotoState(S.DRAINING) elseif btnEdge[3] == 1 then -- Правая кнопка → закрыть внешнюю дверь и начать осушение setOutputs(false, false, true, -100) gotoState(S.DRAINING) end -- DRAINING elseif state == S.DRAINING then setOutputs(false, false, true, -100) setStatus(S.DRAINING) if btnEdge[3] == 1 then -- Правая кнопка → отмена осушения setOutputs(false, false, false, 0) gotoState(S.IDLE) end if water <= 5 and stateTimer >= MIN_DRAIN_TIME then openIntDoor = true doorTimer = 0.0 gotoState(S.IDLE) end -- FAULT elseif state == S.FAULT then setOutputs(false, false, false, 0) setStatus(S.FAULT) end end