diff --git a/AirlockRTL.lua b/AirlockRTL.lua index ceb8fb8..a1fbabc 100644 --- a/AirlockRTL.lua +++ b/AirlockRTL.lua @@ -1,258 +1,278 @@ --- 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 заполнен) +-- 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/timer → закрыть внутрь → осушить (btn4 = пропуск) → открыть внешнюю +-- → выйти → btn4/timer → закрыть внешнюю → IDLE +-- +-- Сценарий FLOW_IN (в отсек): +-- btn5 → затопить (btn4 = пропуск) → открыть внешнюю → войти +-- → btn4/timer → закрыть внешнюю → осушить (btn4 = пропуск) +-- → открыть внутрь → войти → btn4/timer → закрыть внутрь → IDLE +-- +-- 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 -- Снаружи в сухой + +-- Параметры (шкала воды: 0 = пусто, 100 = полно) +local WAIT_TIMEOUT = 5 -- секунд ожидания у открытой двери +local DRAIN_THRESHOLD = 5 -- почти пусто (выход в море) +local INNER_THRESHOLD = 10 -- можно открыть внутрь (по запросу: вода ниже 10%) +local FLOOD_THRESHOLD = 95 -- почти полно (вход снаружи) +local WATER_OPEN_SAFE = 10 -- при btn3: если воды > 10%, сначала осушить + +-- Переменные состояния +local state = ST_IDLE +local flow = FLOW_NONE +local timer = 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("Готов", "0,255,0") + state = ST_IDLE + timer = 0 +end + +-- Инициализация при старте +out[1] = 0 +out[2] = 0 +out[3] = 0 +setStatus("Готов", "0,255,0") 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 + dt = dt 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 + -- Чтение входов + 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 - stateTimer = stateTimer + dt + -- Детекция фронта кнопок (только нажатие, не удержание) + 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 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 + -- Флаг: была ли кнопка 4 обработана в машине состояний + local press4consumed = false - -- === Логика состояний === + -- === Машина состояний === - -- 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 + if state == ST_IDLE then + -- IDLE: ожидание команд. Обе двери закрыты, оборудование выключено. + if press3 then + -- Вход из сухого отсека: если есть вода — сначала осушаем, затем внутрь + if water > WATER_OPEN_SAFE then + setStatus("Осушение перед входом...", "0,200,255") 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) + out[4] = -100 + flow = FLOW_OUT + state = ST_DRAIN_RET + else + setStatus("Открытие внутр. двери...", "0,255,0") + out[2] = 1 + flow = FLOW_OUT + state = ST_OPEN_IN + end + elseif press5 then + -- Вход снаружи → заполнить шлюз водой, затем открыть внешнюю дверь + setStatus("Заполнение шлюза...", "255,200,0") + 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("Осушение для выхода...", "0,200,255") + out[3] = 1 + out[4] = -100 + state = ST_DRAIN_EXIT + else + setStatus("Осушение для отсека...", "0,200,255") + 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("Внутр. дверь открыта. Войдите.", "0,255,0") + 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("Закрытие внутр. двери...", "255,200,0") + out[2] = 0 + state = ST_CLOSE_IN + end + + elseif state == ST_CLOSE_IN then + -- Закрытие внутренней двери. Ждём полного закрытия. + if not doorIn then + if flow == FLOW_OUT then + -- Сценарий "в море": после входа → осушение и выход + setStatus("Осушение для выхода...", "0,200,255") + out[3] = 1 + out[4] = -100 + state = ST_DRAIN_EXIT + else + -- Вернулись в отсек: завершено + setStatus("Готов", "0,255,0") + 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 then + press4consumed = press4 + setStatus("Открытие внеш. двери...", "0,200,255") + 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 then + press4consumed = press4 + setStatus("Открытие внеш. двери...", "255,200,0") + out[3] = 0 + out[1] = 1 + state = ST_OPEN_OUT + end + + elseif state == ST_DRAIN_RET then + -- Осушение шлюза для входа в сухой отсек (можно раньше, порог INNER_THRESHOLD). + out[3] = 1 + out[4] = -100 + if water <= INNER_THRESHOLD or press4 then + press4consumed = press4 + setStatus("Открытие внутр. двери...", "0,255,0") + 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("Внеш. дверь открыта. Выходите.", "0,200,255") + 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("Закрытие внеш. двери...", "255,200,0") + out[1] = 0 + state = ST_CLOSE_OUT + end + + elseif state == ST_CLOSE_OUT then + -- Закрытие внешней двери. Ждём полного закрытия. + if not doorOut then + if flow == FLOW_IN then + -- Сценарий "снаружи": после закрытия — осушение и вход в отсек + setStatus("Осушение после входа...", "0,200,255") + out[3] = 1 + out[4] = -100 + state = ST_DRAIN_RET + else + -- Сценарий "наружу": завершён + setStatus("Готов", "0,255,0") + state = ST_IDLE + flow = FLOW_NONE + end + end + end + + table.clear(inp) end