Данная статья является освещением одного из возможных подходов по обходу ограничений штатного оборудования СКУД.
Исходная задача – интеграция со СКУД «Parsec» с 1С для возможности создания рабочих мест управления доступами с гибкой настройкой возможных действий пользователей. В ходе решения задачи, возникла проблема выдачи карт и определения пользователя карты. Стандартные считыватели турникетов воспринимают 2 стандарта карт: NFC (Mifare) и EMarin. В то же время настольные считыватели, которые были в наличии, могли считывать, либо карты стандарта NFC, либо карты EMarine. Решение с двумя считывателями у оператора, конечно, было сделано. Под считыватели были реализованы свои внешние компоненты 1C, удовлетворяющие стандартам подключаемого оборудования. Но, возникла неприятная ситуация: если на рабочем месте запускается управляющая программа СКУД «Parsec», то считыватели для 1С блокируются. Более того, считыватель NFC подключался к родной оболочке Parsec довольно «экстравагантным способом» - через специальное ПО, которое передавало ID считанной карты через буфер обмена. Это «решения» порядком раздражало операторов пропускной системы. При этом в этом ПО был зашит один алгоритм, а с течением времени, понадобилось расширить кодировку ID, для расширения количество карт. Во внешней компоненте, обслуживающей оборудование NFC, была сделана доработка, и ID в 1С передавался корректно, но… проблемы блокировок, настройки разрешений и два считывателя (не дешевых) на столе продолжали «портить кровь».
В результате долгих и безуспешных поисков решения два в одном, без блокировок и за относительно не большие $$$, был сделан вывод: надо паять!
В качестве источника идей, конечно же выступила платформа Arduino, а поставщиком «проверенных решений» Aliexpress)
Главным критерии выполняемого решения:
- Исключить написание каких-либо драйверов подключаемого оборудования для 1С. Воспользоваться уже имеющимися «из коробки» драйверами библиотеки подключаемого оборудования.
- Один считыватель передает ID для обоих видов карт NFC и EMarine
- Считыватель должен состоять из доступных компонентов и быть легко повторяемым (что бы по каким-то причинам, вышедшие из строя устройства, можно было их быстро заменить)
- Можно удаленно или несложно обновлять прошивку, в случае каких-то изменений
В результате проработки задачи был получен следующий прототип решения:
- На борту Arduino имеется виртуальный COM-порт, по данному порту осуществляется как прошивка, так и передача данных. В составе библиотеки подключаемого оборудования есть драйвер считывателя магнитных карт, который, как раз и является очень примитивным читателем COM-порта. Остается только написать скетч для Arduino выдачи в COM-порт строк нужного формата.
Выбор железок:
Основываясь на собственном опыте, в качестве «сердца» устройства был выбран модуль Arduino Pro Micro. У меня, почему-то, к нему сложилось очень доверительное отношение, т.к. в устройствах «малой домашней автоматизации», он ни разу меня не подводил, в отличие от однополчан рода Atmega328:
Для считывания ID EMarine остановился на модуле RDM6300. Довольно стабильная и неприхотливая железка:
А вот со считыванием NFC-идентификаторов пришлось повозиться. Изначально выбор был сделан в пользу широко известного модуля RC-522. Но по каким-то причинам, от модуля к модулю возникали проблемы с дальностью считывания. После некоторого промежутка работы, модули начинали зависать или вести себя «неадекватно».
Проведя ряд тестовых операций различных модулей из поднебесной, остановился на модуле PN-532. Немного дороже, но зато имеет хорошую дальность действия – 2-4 см и стабильность работы.
Сборка:
Для сборки конструкции выбрал самый обычный корпус 142х82х38мм:
Разработал несложную схему и оттрасировал печатную плату, которую в дальнейшем заказал опять же в том-же городе-герое Китай. Убрал ужасы схемотехники под кат
Спустя пару недель, труженики Китая, не покладая рук, изготовили и прислали законченные платы
Собрал, обработал напильником и получил следующий девайс
Написал скетч для Arduino и залил его в устройство.
/////////////////////////////////////////
// sketch.cpp
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>
#include <string.h>
#include "rdm6300_1.h"
#include "RFIDData.h"
#define PIN_RED 4
#define PIN_GREEN 2
#define PIN_BLUE 3
#define PIN_BUZZER 5
#define PN532_SCK (15)
#define PN532_MOSI (16)
#define PN532_SS (10)
#define PN532_MISO (14)
// Init array that will store new NUID
byte nuidPICC[4];
Rdm6300 rdm6300;
Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS);
uint8_t prev_uid[] = { 0, 0, 0, 0, 0, 0, 0 };
char serial[8] = { "" };
RFIDData currentData;
void setup()
{
Serial.begin(115200);
pinMode(PIN_RED, OUTPUT);
pinMode(PIN_GREEN, OUTPUT);
pinMode(PIN_BLUE, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (!versiondata) {
Serial.print("Didn't find PN53x board");
for (int i = 0; i < 3; i++)
{
digitalWrite(PIN_RED, HIGH);
digitalWrite(PIN_BUZZER, HIGH);
delay(100);
digitalWrite(PIN_RED, LOW);
digitalWrite(PIN_BUZZER, LOW);
delay(100);
}
while (1); // halt
}
// configure board to read RFID tags
nfc.SAMConfig();
delay(100);
rdm6300.begin();
digitalWrite(PIN_RED, HIGH);
digitalWrite(PIN_BUZZER, HIGH);
delay(100);
digitalWrite(PIN_RED, LOW);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(PIN_GREEN, HIGH);
delay(100);
digitalWrite(PIN_GREEN, LOW);
digitalWrite(PIN_BUZZER, HIGH);
digitalWrite(PIN_BLUE, HIGH);
delay(100);
digitalWrite(PIN_BLUE, LOW);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(PIN_RED, HIGH);
}
void loop()
{
//mifare
readMF();
//EM Marin
readEM();
if (currentData.hasNewSerial()) {
Serial.print(currentData.getType());
Serial.print("#");
Serial.println(currentData.getSerial());
}
delay(100);
}
void readEM() {
/* if non-zero tag_id, update() returns true- a new tag is near! */
if (rdm6300.update()) {
digitalWrite(PIN_RED, LOW);
digitalWrite(PIN_BLUE, HIGH);
digitalWrite(PIN_BUZZER, HIGH);
delay(200);
digitalWrite(PIN_BLUE, LOW);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(PIN_RED, HIGH);
currentData.setCurrentSerial(rdm6300.get_tag_id());
}
}
void readMF() {
uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
// Wait for an ISO14443A type cards (Mifare, etc.). When one is found
// 'uid' will be populated with the UID, and uidLength will indicate
// if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 100);
if (success) {
int i;
if (memcmp(prev_uid, uid, 7) == 0) return;
memcpy(prev_uid, uid, 7);
//Info
digitalWrite(PIN_RED, LOW);
digitalWrite(PIN_GREEN, HIGH);
digitalWrite(PIN_BUZZER, HIGH);
delay(200);
digitalWrite(PIN_GREEN, LOW);
digitalWrite(PIN_BUZZER, LOW);
digitalWrite(PIN_RED, HIGH);
currentData.setCurrentSerial(uid, uidLength);
}
else {
memset(prev_uid, 0, 7);
}
}
/**
* Helper routine to dump a byte array as hex values to Serial.
*/
void printHex(byte* buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
if (i < 4) {
//serial[]
}
}
}
/**
* Helper routine to dump a byte array as dec values to Serial.
*/
void printDec(byte* buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], DEC);
}
}
void dump_byte_array(byte* buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
}
}
/////////////////////////////////////////
// RFFIDData.h
#ifndef _RFFIDData_h
#define _RFFIDData_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "arduino.h"
#else
#include "WProgram.h"
#endif
#define USE_NEW_ALG
//#define ALG_TEST
//#define DBG
const static uint8_t CRCTBL[256] = {
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
};
class RFIDData
{
public:
void setCurrentSerial(uint32_t serial);
void setCurrentSerial(byte* buffer, byte bufferSize);
int getType() {
return type;
}
char* getSerial() {
_hasNewSerial = false;
return serial;
}
bool hasNewSerial() {
return _hasNewSerial;
};
private:
int type = 0;
char serial[9] = { '0' };
bool _hasNewSerial = false;
void clearSerial() {
for (int i = 0; i < 9; i++) {
serial[i] = '0';
}
serial[8] = 0;
}
};
#endif
/////////////////////////////////////////
// RFFIDData.cpp
#include "RFIDData.h"
void RFIDData::setCurrentSerial(uint32_t serial_num) {
char tmp_buf[16] = { 0, };
type = 1;
clearSerial();
int cnt = 0;
ultoa(serial_num, tmp_buf, 16);
#ifdef DBG
Serial.println("TMP_Buffer:");
for (int i = 0; i < 16; i++)
{
if (tmp_buf[i] < 20) {
Serial.print("_");
continue;
}
Serial.print(tmp_buf[i]);
}
Serial.println();
Serial.print("len: ");
Serial.println(strlen(tmp_buf));
#endif // DBG
int startpos = 8 - strlen(tmp_buf);
while (tmp_buf[cnt]) {
serial[cnt + startpos] = toUpperCase(tmp_buf[cnt]);
cnt++;
if (cnt > 6) break;
}
_hasNewSerial = true;
}
void RFIDData::setCurrentSerial(byte* buffer, byte bufferSize) {
type = 0;
#ifdef USE_NEW_ALG
//Translate by alg
uint8_t code[4];
uint8_t crc = 0x5A;
uint8_t reverseBuffer[7];
memset(reverseBuffer, 0, sizeof(uint8_t) * 7);
for (int i = 0; i < bufferSize; i++)
{
if (i >= 7) break;
//reverseBuffer[i] = buffer[bufferSize - 1 - i];
reverseBuffer[i] = buffer[i];
}
#ifdef ALG_TEST
////IN: 00901B74028905
////OUT: 6F92896A
//IN: 00013EB95A8D05
//OUT: 875B8D1F
reverseBuffer[6] = 0x00;
reverseBuffer[5] = 0x01;
reverseBuffer[4] = 0x3E;
reverseBuffer[3] = 0xB9;
reverseBuffer[2] = 0x5A;
reverseBuffer[1] = 0x8D;
reverseBuffer[0] = 0x05;
#endif // ALG_TEST
if (bufferSize == 7) {
memset(serial, 0, 8);
for (int i = 0; i < 7; i++) {
crc = CRCTBL[crc ^ reverseBuffer[i]];
#ifdef ALG_TEST
Serial.print("crc: ");
Serial.print(crc);
Serial.print("; index: ");
Serial.print(crc ^ buffer[i]);
Serial.print("; table val: ");
Serial.print(CRCTBL[crc ^ buffer[i]]);
Serial.print("; i: ");
Serial.print(i);
Serial.print("; buf value: ");
Serial.println(buffer[i], HEX);
#endif // ALG_TEST
}
code[0] = reverseBuffer[0] ^ crc;
code[1] = reverseBuffer[1] ^ reverseBuffer[6];
code[2] = reverseBuffer[2] ^ reverseBuffer[5];
code[3] = reverseBuffer[3] ^ reverseBuffer[4];
for (int i = 4; i > 0; i--) {
sprintf(serial, "%s%02X", serial, code[i-1]);
}
type = 2;
_hasNewSerial = true;
return;
}
#endif // USE_NEW_ALG
if (bufferSize > 3) {
type = 2;
memset(serial, 0, 8);
for (int idx = 3; idx >= 0; idx--) {
sprintf(serial, "%s%02X", serial, buffer[idx]);
}
_hasNewSerial = true;
}
}
Далее запустил терминал для проверки считывания карт:
Здесь цифра впереди показывает, с какого устройства (какого типа карта) произведено чтение карты (NFC, EMarine)
А дальше дело оставалось за малым, написать код обработчика 1С для внешнего события стандартного считывателя магнитных карт и подключить устройство:
В качестве прошивальщика выступила оболочка XLoader, которая без проблем «заливает» hex-файл в ардуинку.
Вот таким незатейливым способом удалось «победить» нестыковки родного оборудования СКУД «Parsec».
Итоговая стоимость 1 считывателя вышла не больше 1 800 руб. В отличие от фирменного считывателя за 11 000 руб. Причем, проблема драйверов и блокировок для фирменного считывателя по прежнему решена не будет.