http://habrahabr.ru/post/240647/
Не знаю, как вам, но мне в течении дня приходится часто отходить от рабочего места и блокировать мак. Чтобы не совершать несколько кликов мышкой, блокировку своего мака я «повесил» на клавиши «shift + cmd + l», но по приходу к рабочему месту опять же приходилось вводить пароль (который в силу моей параноидальности не так-то прост). И вот, ошибившись в спешке в очередной раз при его вводе, задумался автоматизировать процесс блокировки/разблокировки. Так как все двери нашего офиса открываются по карте, решил повесить на RFID-метку (всё равно всё время болтается на шее) и эту функцию. Итак, задача на словах выглядела так: авторизовавшись единожды в начале рабочего дня иметь возможность блокировки/разблокировки мака по RFID-метке, при этом все функции проверки валидности метки и т.п. должны происходить на стороне мака.
Начало — уже половина дела, да и как раз под рукой освободился стенд на базе Arduino UNO.
В процессе работы решил дополнить функционал: считывание метки будет происходить только при нажатой кнопке жёлтого цвета на фото выше (уж не знаю, зачем такие усложнения — видимо, опять параноя сказывается). Итак, общий процесс должен будет выглядеть следующим образом:
- Функциональная часть вся будет на стороне мака, а Arduino будет только передавать код метки и «мигать светодиодами»;
- Зажимаем кнопку — загорается жёлтый светодиод готовности;
- Если прикладываем некорректную метку — загорается красный светодиод;
- Прикладываем правильную метку — загорается зелёный светодиод и происходит блокировка/разблокировка мака.
Докупив модуль RFID на 125 кГц, собрал на макетной плате прототип устройства.
Скетч и код для Arduino#include <SoftwareSerial.h>
// "Распиновка"
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») необходимо дописать перед закрывающими тегами "</dict></plist>":
<key>LSBackgroundOnly</key>
</true>
...И добавляем запуск нашей программы при загрузке системы P.S.: Поигравшись пару дней с прототипом, решил, что он «имеет право жить» — осталось спаять его в корпус, с использованием меньшего брата Arduino Nano.
N.B.: В XXI веке будет много завещаний, содержащих пароли.