-- AirlockRTL.lua — Контроллер шлюза (State Machine) -- ================================================= -- -- Физическая схема: -- [Сухой отсек] — [Внутр.дверь] — [ШЛЮЗ] — [Наруж.дверь] — [Снаружи/вода] -- -- Входы: -- 1 — состояние наружней (левой) двери (1=открыта) -- 2 — состояние внутренней (правой) двери (1=открыта) -- 3 — кнопка внутри шлюза (слева) — вход в шлюз из сухого отсека -- 4 — кнопка внутри шлюза (центр) — выход из шлюза (контекстно) -- 5 — кнопка снаружи — вход в шлюз снаружи -- 6 — уровень воды в шлюзе (0 = пусто, 100 = полно) -- -- Выходы: -- 1 — наружняя дверь (1=открыть, 0=закрыть) -- 2 — внутренняя дверь (1=открыть, 0=закрыть) -- 3 — насос (1=вкл, 0=выкл) -- 4 — целевой уровень воды (-100=осушить, +100=заполнить) -- 5 — текст статуса -- 6 — цвет текста (rrr,ggg,bbb) -- -- Принцип: любое нажатие сначала управляет водой, затем дверью. -- Слева всегда море (вода), справа отсек (сухо). -- Направление (flow) запоминается при входе. -- -- Сценарий FLOW_OUT (в море): -- btn3 → если воды >10% — осушить → открыть внутрь → войти -- → авто/btn4 → закрыть внутрь → затопить → открыть внешнюю -- → выйти → авто/btn4 → закрыть внешнюю → IDLE -- -- Сценарий FLOW_IN (в отсек): -- btn5 → затопить → открыть внешнюю → войти -- → авто/btn4 → закрыть внешнюю → осушить → открыть внутрь -- → войти → авто/btn4 → закрыть внутрь → IDLE -- -- btn4/timer: таймер 2с; btn4 = пропустить ожидание сейчас. -- В DRAIN/FLOOD: btn4 или таймаут 15с — пропустить и открыть дверь. -- btn4 из IDLE: направление по flow (запомнен при входе), иначе по воде. -- Состояния автомата local ST_IDLE = 0 -- Ожидание команд local ST_OPEN_IN = 1 -- Открытие внутренней двери local ST_WAIT_IN = 2 -- Ожидание прохода через внутреннюю дверь local ST_CLOSE_IN = 3 -- Закрытие внутренней двери local ST_DRAIN_EXIT = 4 -- Осушение для выхода наружу local ST_FLOOD = 5 -- Заполнение для входа снаружи local ST_DRAIN_RET = 6 -- Осушение для возврата в сухой отсек local ST_OPEN_OUT = 7 -- Открытие внешней двери local ST_WAIT_OUT = 8 -- Ожидание прохода через внешнюю дверь local ST_CLOSE_OUT = 9 -- Закрытие внешней двери -- Сценарии local FLOW_NONE = 0 local FLOW_OUT = 1 -- Из сухого наружу local FLOW_IN = 2 -- Снаружи в сухой -- Статусы (с ведущим пробелом для отступа на дисплее) local S_READY = "Готов" local S_DRAIN_PRE = "Осушение перед входом" local S_OPEN_IN = "Открытие внутренней двери" local S_WAIT_IN = "Внутренняя дверь открыта" local S_CLOSE_IN = "Закрытие внутренней двери" local S_FLOOD = "Заполнение шлюза" local S_DRAIN_OUT = "Осушение для выхода" local S_DRAIN_RET = "Осушение для отсека" local S_OPEN_OUT = "Открытие внешней двери" local S_WAIT_OUT = "Внешняя дверь открыта" local S_CLOSE_OUT = "Закрытие внешней двери" local S_DRAIN_AFTER = "Осушение после входа" -- Цвета local C_GREEN = "0,255,0" local C_CYAN = "0,200,255" local C_YELLOW = "255,200,0" -- Параметры (шкала воды: 0 = пусто, 100 = полно) local WAIT_TIMEOUT = 3 -- секунд ожидания у открытой двери local PUMP_TIMEOUT = 15 -- макс. секунд работы насоса до принудительного продолжения local DRAIN_THRESHOLD = 10 -- почти пусто (выход в море) local INNER_THRESHOLD = 10 -- можно открыть внутрь (по запросу: вода ниже 10%) local FLOOD_THRESHOLD = 90 -- почти полно (вход снаружи) local WATER_OPEN_SAFE = 10 -- при btn3: если воды > 10%, сначала осушить -- Переменные состояния local state = ST_IDLE local flow = FLOW_NONE local timer = 0 local pumpTimer = 0 -- сколько работает насос в текущем цикле -- Предыдущие значения кнопок для детекции фронта local prev3 = false local prev4 = false local prev5 = false -- Установка текста и цвета статуса local function setStatus(text, color) out[5] = " " .. text out[6] = color end -- Сброс в IDLE: закрыть двери, выключить насос, контекст (flow) сохраняется local function goIdle() out[1] = 0 out[2] = 0 out[3] = 0 setStatus(S_READY, C_GREEN) state = ST_IDLE timer = 0 end -- Инициализация при старте out[1] = 0 out[2] = 0 out[3] = 0 setStatus(S_READY, C_GREEN) inp = {} -- Главная функция, вызывается каждый кадр function upd(dt) dt = dt or 0 -- Чтение входов local doorOut = inp[1] == 1 local doorIn = inp[2] == 1 local btn3 = inp[3] == 1 local btn4 = inp[4] == 1 local btn5 = inp[5] == 1 local water = inp[6] or 0 -- Детекция фронта кнопок (только нажатие, не удержание) local press3 = btn3 and not prev3 local press4 = btn4 and not prev4 local press5 = btn5 and not prev5 prev3 = btn3 prev4 = btn4 prev5 = btn5 -- Сброс таймера насоса при входе в состояния с насосом if state == ST_DRAIN_EXIT or state == ST_FLOOD or state == ST_DRAIN_RET then pumpTimer = pumpTimer + dt else pumpTimer = 0 end -- Флаг: была ли кнопка 4 обработана в машине состояний local press4consumed = false -- === Машина состояний === if state == ST_IDLE then -- IDLE: ожидание команд. Обе двери закрыты, оборудование выключено. if press3 then -- Вход из сухого отсека: если есть вода — сначала осушаем, затем внутрь if water > WATER_OPEN_SAFE then setStatus(S_DRAIN_PRE, C_CYAN) out[3] = 1 out[4] = -100 flow = FLOW_OUT state = ST_DRAIN_RET else setStatus(S_OPEN_IN, C_GREEN) out[2] = 1 flow = FLOW_OUT state = ST_OPEN_IN end elseif press5 then -- Вход снаружи → заполнить шлюз водой, затем открыть внешнюю дверь setStatus(S_FLOOD, C_YELLOW) out[3] = 1 out[4] = 100 flow = FLOW_IN state = ST_FLOOD elseif press4 then -- Выход из шлюза. Направление: flow (запомнен при входе), иначе уровень воды. press4consumed = true local dir = flow if dir == FLOW_NONE then dir = (water <= DRAIN_THRESHOLD) and FLOW_OUT or FLOW_IN flow = dir end if dir == FLOW_OUT then setStatus(S_DRAIN_OUT, C_CYAN) out[3] = 1 out[4] = -100 state = ST_DRAIN_EXIT else setStatus(S_DRAIN_RET, C_CYAN) out[3] = 1 out[4] = -100 state = ST_DRAIN_RET end end elseif state == ST_OPEN_IN then -- Открытие внутренней двери. Ждём сигнала от двери. out[2] = 1 if doorIn then timer = 0 setStatus(S_WAIT_IN, C_GREEN) state = ST_WAIT_IN end elseif state == ST_WAIT_IN then -- Внутренняя дверь открыта. Ждём прохода человека (btn4 или таймер). out[2] = 1 timer = timer + dt if press4 or timer >= WAIT_TIMEOUT then press4consumed = press4 setStatus(S_CLOSE_IN, C_YELLOW) out[2] = 0 state = ST_CLOSE_IN end elseif state == ST_CLOSE_IN then -- Закрытие внутренней двери. Ждём полного закрытия. if not doorIn then if flow == FLOW_OUT then -- Сценарий "в море": после входа → заполнить (давление) и открыть внешнюю setStatus(S_FLOOD, C_YELLOW) out[3] = 1 out[4] = 100 state = ST_FLOOD else -- Вернулись в отсек: завершено setStatus(S_READY, C_GREEN) flow = FLOW_NONE state = ST_IDLE end end elseif state == ST_DRAIN_EXIT then -- Осушение шлюза перед выходом в море. out[3] = 1 out[4] = -100 if water <= DRAIN_THRESHOLD or press4 or pumpTimer >= PUMP_TIMEOUT then press4consumed = press4 setStatus(S_OPEN_OUT, C_CYAN) out[3] = 0 out[1] = 1 state = ST_OPEN_OUT end elseif state == ST_FLOOD then -- Заполнение шлюза для входа снаружи (уравнивание давления). out[3] = 1 out[4] = 100 if water >= FLOOD_THRESHOLD or press4 or pumpTimer >= PUMP_TIMEOUT then press4consumed = press4 setStatus(S_OPEN_OUT, C_YELLOW) out[3] = 0 out[1] = 1 state = ST_OPEN_OUT end elseif state == ST_DRAIN_RET then -- Осушение шлюза для входа в сухой отсек. out[3] = 1 out[4] = -100 if water <= INNER_THRESHOLD or press4 or pumpTimer >= PUMP_TIMEOUT then press4consumed = press4 setStatus(S_OPEN_IN, C_GREEN) out[3] = 0 out[2] = 1 state = ST_OPEN_IN end elseif state == ST_OPEN_OUT then -- Открытие внешней двери. Ждём сигнала от двери. out[1] = 1 if doorOut then timer = 0 setStatus(S_WAIT_OUT, C_CYAN) state = ST_WAIT_OUT end elseif state == ST_WAIT_OUT then -- Внешняя дверь открыта. Ждём прохода человека (btn4 или таймер). out[1] = 1 timer = timer + dt if press4 or timer >= WAIT_TIMEOUT then press4consumed = press4 setStatus(S_CLOSE_OUT, C_YELLOW) out[1] = 0 state = ST_CLOSE_OUT end elseif state == ST_CLOSE_OUT then -- Закрытие внешней двери. Ждём полного закрытия. if not doorOut then if flow == FLOW_IN then -- Сценарий "снаружи": после закрытия — осушение и вход в отсек setStatus(S_DRAIN_AFTER, C_CYAN) out[3] = 1 out[4] = -100 state = ST_DRAIN_RET else -- Сценарий "наружу": завершён setStatus(S_READY, C_GREEN) state = ST_IDLE flow = FLOW_NONE end end end table.clear(inp) end