Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- doorlock - a touch PIN lock for an ADVANCED computer (not pocket).
- --
- -- First run: pick a PIN on the keypad; the program takes over startup
- -- so the computer boots locked from then on. Ctrl+T is blocked, and
- -- attempts/lockout survive reboots.
- --
- -- 3 wrong PINs = locked out. The unlock code is rednet-messaged to the
- -- pocket computer OWNER_ID (it lands in the Messages app) - the door
- -- computer needs a wireless modem for that. With no modem it falls
- -- back to a 60 second cooldown.
- --
- -- When unlocked: big OPEN DOOR button (your redstone goes in
- -- openDoor() below), plus Lock and Shell buttons in the corners.
- -------------------------------------------------- CONFIG - edit me --
- local OWNER_ID = 11 -- pocket computer that receives unlock codes
- local DOOR_SIDE = "bottom" -- redstone side used by the default openDoor
- local DOOR_TIME = 3 -- seconds the door stays powered
- local AUTO_LOCK = 60 -- relock after this many idle seconds (0 = never)
- local door = peripheral.find("redstoneIntegrator")
- -- The OPEN DOOR button runs this. Put YOUR redstone logic here.
- local function openDoor()
- door.setOutput(DOOR_SIDE, true)
- sleep(DOOR_TIME)
- door.setOutput(DOOR_SIDE, false)
- end
- -----------------------------------------------------------------------
- local DATA = "/.doorlock"
- if not term.isColor or not term.isColor() then
- print("doorlock needs an advanced (gold) computer.")
- return
- end
- math.randomseed(os.epoch and os.epoch("utc") or os.time() * 1000)
- local hasModem = false
- for _, side in ipairs(peripheral.getNames()) do
- if peripheral.getType(side) == "modem" then
- if pcall(rednet.open, side) then hasModem = true end
- end
- end
- local w, h = term.getSize()
- local function loadState()
- if fs.exists(DATA) then
- local f = fs.open(DATA, "r")
- local v = textutils.unserialize(f.readAll() or "")
- f.close()
- if type(v) == "table" then return v end
- end
- return {}
- end
- local st = loadState()
- local function save()
- local f = fs.open(DATA, "w")
- f.write(textutils.serialize(st))
- f.close()
- end
- local function hash(s)
- local n = 5381
- for i = 1, #s do
- n = (n * 33 + s:byte(i)) % 4294967296
- end
- return tostring(n)
- end
- local function fill(x, y, wd, ht, bg)
- term.setBackgroundColor(bg)
- for i = 0, ht - 1 do
- term.setCursorPos(x, y + i)
- term.write(string.rep(" ", wd))
- end
- end
- local function center(y, s, fg)
- term.setCursorPos(math.floor((w - #s) / 2) + 1, y)
- term.setTextColor(fg)
- term.setBackgroundColor(colors.black)
- term.write(s)
- end
- local function sendCode(code)
- if not hasModem then return false end
- rednet.send(OWNER_ID, {
- text = "3 wrong PIN tries on #" .. os.getComputerID()
- .. ". Unlock code: " .. code,
- label = os.getComputerLabel() or "Door Lock",
- }, "pocketos")
- return true
- end
- -- GUI number pad. Returns the digits entered once OK/Enter is pressed.
- -- opts: hint (bottom line), onResend (adds a resend-code button).
- local function keypad(title, msg, opts)
- opts = opts or {}
- local entry = ""
- local info = msg
- local infoColor = colors.red
- local layout = {
- { "1", "2", "3" },
- { "4", "5", "6" },
- { "7", "8", "9" },
- { "C", "0", "OK" },
- }
- while true do
- term.setBackgroundColor(colors.black)
- term.clear()
- center(2, title, colors.white)
- if #entry > 0 then
- center(4, string.rep("\7 ", #entry), colors.blue)
- else
- center(4, "_ _ _", colors.gray)
- end
- local regions = {}
- local x0 = math.floor(w / 2) - 8
- for r = 1, 4 do
- for cn = 1, 3 do
- local label = layout[r][cn]
- local bx = x0 + (cn - 1) * 6
- local by = 4 + r * 2
- fill(bx, by, 5, 1, label == "OK" and colors.green or colors.gray)
- term.setCursorPos(bx + math.floor((5 - #label) / 2), by)
- term.setTextColor(colors.white)
- term.write(label)
- regions[#regions + 1] = { x1 = bx, x2 = bx + 4, y = by, label = label }
- end
- end
- if info then center(14, info, infoColor) end
- if opts.onResend then
- center(16, "[ Resend code ]", colors.blue)
- end
- center(h, opts.hint or "Typing digits works too", colors.gray)
- local function press(label)
- if label == "OK" then
- if #entry >= 3 then return entry end
- info, infoColor = "At least 3 digits", colors.red
- elseif label == "C" then
- entry = ""
- elseif #entry < 8 then
- entry = entry .. label
- end
- end
- local e, a, b, c = os.pullEventRaw() -- raw: Ctrl+T is ignored
- if e == "mouse_click" then
- local handled = false
- for _, r in ipairs(regions) do
- if c == r.y and b >= r.x1 and b <= r.x2 then
- local done = press(r.label)
- if done then return done end
- handled = true
- break
- end
- end
- if not handled and opts.onResend and c == 16 then
- opts.onResend()
- info, infoColor = "Code sent again!", colors.green
- end
- elseif e == "char" and a:match("^%d$") then
- if #entry < 8 then entry = entry .. a end
- elseif e == "key" then
- if a == keys.enter then
- if #entry >= 3 then return entry end
- info, infoColor = "At least 3 digits", colors.red
- elseif a == keys.backspace then
- entry = entry:sub(1, -2)
- end
- end
- end
- end
- local function cooldown(secs)
- for left = secs, 1, -1 do
- term.setBackgroundColor(colors.black)
- term.clear()
- center(8, "Too many attempts", colors.red)
- center(10, "No modem to send a code.", colors.gray)
- center(12, "Try again in " .. left .. "s", colors.white)
- local t = os.startTimer(1)
- repeat
- local e, id = os.pullEventRaw()
- until e == "timer" and id == t
- end
- end
- local function unlockedScreen()
- while true do
- term.setBackgroundColor(colors.black)
- term.clear()
- center(2, (os.getComputerLabel() or "Door") .. " - unlocked", colors.white)
- local bw, bh = 25, 5
- local bx = math.floor((w - bw) / 2) + 1
- local by = math.floor((h - bh) / 2) + 1
- fill(bx, by, bw, bh, colors.green)
- term.setCursorPos(bx + math.floor((bw - 9) / 2), by + 2)
- term.setTextColor(colors.white)
- term.write("OPEN DOOR")
- fill(2, h - 1, 6, 1, colors.gray)
- term.setCursorPos(3, h - 1)
- term.setTextColor(colors.white)
- term.write("Lock")
- fill(w - 8, h - 1, 7, 1, colors.gray)
- term.setCursorPos(w - 7, h - 1)
- term.write("Shell")
- local timer = AUTO_LOCK > 0 and os.startTimer(AUTO_LOCK) or nil
- local e, a, b, c = os.pullEventRaw()
- if e == "timer" and a == timer then
- return "lock" -- idle too long
- elseif e == "mouse_click" then
- if b >= bx and b <= bx + bw - 1 and c >= by and c <= by + bh - 1 then
- fill(bx, by, bw, bh, colors.lime)
- term.setCursorPos(bx + math.floor((bw - 10) / 2), by + 2)
- term.setTextColor(colors.black)
- term.write("DOOR OPEN!")
- pcall(openDoor)
- elseif c == h - 1 and b >= 2 and b <= 7 then
- return "lock"
- elseif c == h - 1 and b >= w - 8 then
- return "shell"
- end
- end
- end
- end
- -- first-run setup
- if not st.pin then
- local m
- while true do
- local a = keypad("Set up Door Lock", m, { hint = "Choose a PIN (3-8 digits)" })
- local b = keypad("Set up Door Lock", nil, { hint = "Repeat the PIN" })
- if a == b then
- st.pin = hash(a)
- st.attempts = 0
- save()
- break
- end
- m = "PINs didn't match - retry"
- end
- local f = fs.open("startup.lua", "w")
- f.write("-- Door lock (created by doorlock)\n")
- f.write("shell.run(\"doorlock.lua\")\n")
- f.close()
- end
- local msg
- while true do
- if st.lockout then
- local entered = keypad("LOCKED OUT", msg, {
- hint = "Code sent to pocket #" .. OWNER_ID,
- onResend = hasModem and function() sendCode(st.lockout) end or nil,
- })
- if entered == st.lockout then
- st.lockout = nil
- st.attempts = 0
- save()
- msg = nil
- if unlockedScreen() == "shell" then break end
- else
- msg = "Wrong code"
- end
- else
- local entered = keypad("\7 Locked", msg)
- if hash(entered) == st.pin then
- st.attempts = 0
- save()
- msg = nil
- if unlockedScreen() == "shell" then break end
- else
- st.attempts = (st.attempts or 0) + 1
- save()
- if st.attempts >= 3 then
- if hasModem then
- st.lockout = tostring(math.random(100000, 999999))
- save()
- sendCode(st.lockout)
- msg = nil
- else
- cooldown(60)
- st.attempts = 0
- save()
- msg = nil
- end
- else
- msg = "Wrong PIN - " .. (3 - st.attempts) .. " left"
- end
- end
- end
- end
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.clear()
- term.setCursorPos(1, 1)
- print("Door lock exited. Run 'reboot' to lock again.")
Advertisement
Add Comment
Please, Sign In to add comment