[Из песочницы] Ввод пароля или похитители времени
Не знаю, как вам, но мне в течении дня приходится часто отходить от рабочего места и блокировать мак. Чтобы не совершать несколько кликов мышкой, блокировку своего мака я «повесил» на клавиши «shift + cmd + l», но по приходу к рабочему месту опять же приходилось вводить пароль (который в силу моей параноидальности не так-то прост). И вот, ошибившись в спешке в очередной раз при его вводе, задумался автоматизировать процесс блокировки/разблокировки. Так как все двери нашего офиса открываются по карте, решил повесить на RFID-метку (всё равно всё время болтается на шее) и эту функцию. Итак, задача на словах выглядела так: авторизовавшись единожды в начале рабочего дня иметь возможность блокировки/разблокировки мака по RFID-метке, при этом все функции проверки валидности метки и т.п. должны происходить на стороне мака.
Начало — уже половина дела, да и как раз под рукой освободился стенд на базе Arduino UNO.В процессе работы решил дополнить функционал: считывание метки будет происходить только при нажатой кнопке жёлтого цвета на фото выше (уж не знаю, зачем такие усложнения — видимо, опять параноя сказывается). Итак, общий процесс должен будет выглядеть следующим образом:
Функциональная часть вся будет на стороне мака, а Arduino будет только передавать код метки и «мигать светодиодами»;
Зажимаем кнопку — загорается жёлтый светодиод готовности;
Если прикладываем некорректную метку — загорается красный светодиод;
Прикладываем правильную метку — загорается зелёный светодиод и происходит блокировка/разблокировка мака.
Докупив модуль RFID на 125 кГц, собрал на макетной плате прототип устройства.Скетч и код для Arduino
#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 Вам в помощь)Добавляем новый пароль…
В поле Account Name прописываем адекватное имя — позже к нему будем обращаться из скрипта
Не забываем получить доступ к ключу:
security find-generic-password -ga my password Подтверждаем доступ к ключу для консольной программы 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 Вам в помощь)Прописываем код…
Экспортируем…
Сохраняем как Application
Можно так же добавить ключ, сообщающий о запуске программы в background-режиме. Для этого в файле «info.plist» (доступен при просмоте содержимого папки программы: ctrl + click на файле и выбор «Show Package Contents») необходимо дописать перед закрывающими тегами »»: