[Из песочницы] Ввод пароля или похитители времени

055c4cbbfc4a407393caa5552618d334.jpgНе знаю, как вам, но мне в течении дня приходится часто отходить от рабочего места и блокировать мак. Чтобы не совершать несколько кликов мышкой, блокировку своего мака я «повесил» на клавиши «shift + cmd + l», но по приходу к рабочему месту опять же приходилось вводить пароль (который в силу моей параноидальности не так-то прост). И вот, ошибившись в спешке в очередной раз при его вводе, задумался автоматизировать процесс блокировки/разблокировки. Так как все двери нашего офиса открываются по карте, решил повесить на RFID-метку (всё равно всё время болтается на шее) и эту функцию. Итак, задача на словах выглядела так: авторизовавшись единожды в начале рабочего дня иметь возможность блокировки/разблокировки мака по RFID-метке, при этом все функции проверки валидности метки и т.п. должны происходить на стороне мака.

Начало — уже половина дела, да и как раз под рукой освободился стенд на базе Arduino UNO.В процессе работы решил дополнить функционал: считывание метки будет происходить только при нажатой кнопке жёлтого цвета на фото выше (уж не знаю, зачем такие усложнения — видимо, опять параноя сказывается). Итак, общий процесс должен будет выглядеть следующим образом:

Функциональная часть вся будет на стороне мака, а Arduino будет только передавать код метки и «мигать светодиодами»; Зажимаем кнопку — загорается жёлтый светодиод готовности; Если прикладываем некорректную метку — загорается красный светодиод; Прикладываем правильную метку — загорается зелёный светодиод и происходит блокировка/разблокировка мака. Докупив модуль RFID на 125 кГц, собрал на макетной плате прототип устройства.Скетч и код для Arduino 45fc6fa671864d308a1e4e64a754d65a.jpg #include

// «Распиновка» int buttonPin = 2; int ledGreenPin = 13; int ledYellowPin = 12; int ledRedPin = 11;

// Модуль RFID и переменны для него SoftwareSerial RFID (6, 7);

String inputString = »; int rfidData; String rfidNumber = »; String rfidNumberLast = »; boolean startPressButton = false;

void setup () { Serial.begin (115200); RFID.begin (9600);

pinMode (buttonPin, INPUT);

pinMode (ledGreenPin, OUTPUT); pinMode (ledYellowPin, OUTPUT); pinMode (ledRedPin, OUTPUT);

digitalWrite (ledGreenPin, LOW); digitalWrite (ledYellowPin, LOW); digitalWrite (ledRedPin, LOW); }

void loop () { listenButton (); }

/* Слушаем кнопку. Если нажата — слушаем RFID */ void listenButton () { if (digitalRead (buttonPin) == HIGH) { if (! startPressButton) { startPressButton = true; clearRFID (); } digitalWrite (ledYellowPin, HIGH); listenRFID (); } else { startPressButton = false; digitalWrite (ledYellowPin, LOW); } }

/* Слушаем RFID. Если получен номер метки — кидаем его в поток */ void listenRFID () { if (RFID.available ()) { delay (100); rfidNumber = »; for (int i = 0; i < 14; i++) { rfidData = RFID.read(); if (rfidData < 16) rfidNumber += '0'; rfidNumber += rfidData; } RFID.flush(); sendRDIFNumber(); } }

/* Кидаем номер метки в поток */ void sendRDIFNumber () { if (rfidNumber!= » and rfidNumberLast!= rfidNumber) { Serial.print («S»); Serial.print (rfidNumber); Serial.print («E»); rfidNumberLast = rfidNumber; rfidNumber = »; } }

/* Слушаем поток на предмет комманд для Arduino */ void serialEvent () { while (Serial.available ()) { char inChar = (char)Serial.read (); inputString += inChar; if (inputString == «M1F») { Serial.flush (); inputString = »; logInOutProcess (); } if (inputString == «M0F») { Serial.flush (); inputString = »; logInOutFail (); } } }

/* Проверка на стороне мака прошла успешно — зелёный цвет */ void logInOutProcess () { clearRFID (); digitalWrite (ledGreenPin, HIGH); digitalWrite (ledYellowPin, LOW); digitalWrite (ledRedPin, LOW); delay (1000); digitalWrite (ledGreenPin, LOW); digitalWrite (ledYellowPin, LOW); digitalWrite (ledRedPin, LOW); }

/* Проверка на стороне мака не прошла — красный цвет */ void logInOutFail () { clearRFID (); digitalWrite (ledGreenPin, LOW); digitalWrite (ledYellowPin, LOW); digitalWrite (ledRedPin, HIGH); delay (1000); digitalWrite (ledGreenPin, LOW); digitalWrite (ledYellowPin, LOW); digitalWrite (ledRedPin, LOW); }

/* Чистка выдачи RFID-модуля */ void clearRFID () { RFID.flush (); rfidNumberLast = »; rfidNumber = »; } Самое интересное, на мой взгляд, происходит не на стороне Adruino, а на стороне мака. Итак, общаться со стендом будет Node.js с модулем SerialPort. Но для начала хотелось бы решить вопрос с хранением пароля разблокировки (очень уж не хотелось держать его открытым в теле скрипта, хоть и FileVault по-умолчанию включён). Для этого решил воспользоваться стандартной «ключницей» OS X — Keychain Access.Как добавить пароль в ключницу? Вызываем Keychain Access (Spotlight Search Вам в помощь)1d16238524754f888c9b85fd317ed2bc.jpgДобавляем новый пароль…

64d5b1b6397949569ec7e8c5b0b14d3c.jpgВ поле Account Name прописываем адекватное имя — позже к нему будем обращаться из скрипта

Не забываем получить доступ к ключу:

security find-generic-password -ga my password imageПодтверждаем доступ к ключу для консольной программы security

Ну вот, можно приступить к самому скрипту на Node.js. Для этого на рабочем столе создаём папку «RFIDUnLock», сам скрипт будет именоваться как «rfid.js»: var inputString = »; var serialport = require ('serialport'); var SerialPort = serialport.SerialPort; var sp = new SerialPort ('/dev/tty.usbmodem20331', { // подсмотреть «путь» девайса можно в «Tools/Serial Port» программы Arduino baudrate: 115200 }); var exec = require ('child_process').exec; sp.on ('open', function () { /* Читаем поток */ sp.on ('data', function (data) { inputString += data.toString («utf8»); /* Берём из потока нужные данные по «маркерам» */ var cardCode = inputString.match (/S ([0–9]+)E/i); if (cardCode && cardCode[1] != 'undefined') { checkCardNumber (cardCode[1]); inputString = ''; } }); }); function checkCardNumber (cardCode) { sp.flush (function () { /* Если метка та, что нужно… */ if (cardCode == '0211111111111111111111111103') { /* …отправляем команду Arduino «мигнуть зелёным» */ sp.write ('M1F'); /* проверяем: запущен ли «скрин сейвер»? */ exec ('ps aux | grep -c ScreenSaverEngine.app | grep -v grep', function (error, stdout, stderr) { /* если запущен — берём пароль из Kaychain и «печатаем» в поле ввода пароля */ if (parseInt (stdout) > 2) { exec («security 2>&1 >/dev/null find-generic-password -ga mypassword | ruby -e 'print $1 if STDIN.gets =~ /^password: \»(.*)\»$/'», function (error, stdout, stderr) { if (error!== null) return; var appleScript = 'osascript -e \'tell application «System Events»\' -e \'key code 56\' -e \'delay 0.5\' -e \'keystroke »' + stdout + '»\' -e \'key code 36\' -e \'end tell\''; exec (appleScript); }); /* …если «скрин север» не запущен — запускаем */ } else { exec ('open -a /System/Library/Frameworks/ScreenSaver.framework/Versions/Current/Resources/ScreenSaverEngine.app'); } }); /* Метка не корректна — отправляем команду Arduino «мигнуть красным» */ } else { sp.write ('M0F'); } }); } Далее сохраняем как программу (с помощью Script Editor) код вызова Node.js скрипта:

do shell script »/usr/local/bin/node ~/Desktop/RFIDUnLock/rfid.js» Подробнее, если можно… Вызываем Script Editor (Spotlight Search Вам в помощь)aca039c959854ec4b936ae2d9b6e74d0.jpgПрописываем код…

0afb5beeec5944c39d8b2af0bcf137a8.jpgЭкспортируем…

c09d3527a29e40aaae69d266af557f7b.jpgСохраняем как Application

Можно так же добавить ключ, сообщающий о запуске программы в background-режиме. Для этого в файле «info.plist» (доступен при просмоте содержимого папки программы: ctrl + click на файле и выбор «Show Package Contents») необходимо дописать перед закрывающими тегами »»:

LSBackgroundOnly …И добавляем запуск нашей программы при загрузке системы image P.S.: Поигравшись пару дней с прототипом, решил, что он «имеет право жить» — осталось спаять его в корпус, с использованием меньшего брата Arduino Nano.N.B.: В XXI веке будет много завещаний, содержащих пароли.

© Habrahabr.ru