Перейти к публикации
iT4iT.CLUB
Kitsum

Замок с радиочастотной идентификацией

Рекомендованные сообщения

 

Обновление по просьбе Alex13 находится тут

  1. Открытие/Закрытие разнесены на разные пины микроконтроллера (для управление соленоидом)
Скрытый текст

 

Обновление по просьбе Alex13 находится тут

  1. Добавлен выбор режима закрытия замка (автоматический или по карте)
  2. Добавлена сигнализация оповещающая о попытках подбора ключа

Обновление по просьбе svchekalin находится тут

  1. Добавлен Ethernet Shield на чипе W5100
  2. Ключи теперь хранятся не в EEPROM контроллера, а в MySQL базе на удаленном сервере
  3. ...

nrf_z9.jpg.38a9bbdeb71babfdc66653a8d099b

Внимание: Очень много фотографий в статье!

Начнем с предыстории.

В один обыденный рабочий день ко мне обратились мои друзья и по совместительству коллеги по работе с идеей сделать замок с радиочастотной идентификацией. Электронный замок должен обслуживать дверь в рабочее помещение. Все бы хорошо, но дверь далеко не домашняя, сделана из метала и стоит в металлической коробки. Собрать электронную начинку замка и написать программу для микроконтроллера - решаемая задача, но как механически удержать такого монстра в зафиксированном состоянии?

Но то, что для меня казалось проблемой, было воспринято моими друзьями с ухмылкой, и мне показали здоровый электромагнит. Точно такие же электромагниты используются в домофонах. Даже не хочу знать, откуда они его достали, но меня увиденное воодушевило.

Скрытый текст

nrf_z14.jpg.c2ea95d8d6a6215b1930ae5083c5nrf_z15.jpg.abd99b3ce7edb35efdf99b83b19d

Задача поставлена, приступаем к реализации!

Техническое задание

  1. Замок должен аутентифицировать хозяев без физического контакта пользователя с устройством
  2. Память должна вмещать не менее двух десятков ключей
  3. Устройство должно иметь возможность добавлять в память новые ключи
  4. Добавлять новые ключи можно только, используя специальный, мастер ключ
  5. Мастер ключ должен также иметь возможность открывать замок
  6. Должна быть реализована возможность обнуления списка используемых ключей, в том числе и удаление мастер ключа
  7. Замок должен иметь возможность открываться изнутри по нажатию на кнопку (как в домофоне)
  8. Все описанные пункты должны быть просты в использовании и не требовать знания программирования

Реализация

За основу было решено взять клон Arduino UNO с ATmega328p на борту в паре с RFID (NRF) сканером RC522. Сам сканер является очень удачным выбором т.к работает на частоте 13.56 МГц и поддерживает чипы:

  1. MIFARE S50
  2. MIFARE S70
  3. MIFARE UltraLight
  4. MIFARE Pro
  5. MIFARE DESfire
  6. ...

nrf_z2.jpg.8508ffa5ad9a4870a2cb66b7d1a97

Скрытый текст

nrf_z3.jpg.867d059e0990bd6b1275223d13791

Данные чипы используются в различных карточках метро и прочего общественного транспорта, ключах современных домофонов, различных умных браслетах и носимой электроники. У братьев Китайцев можно купить даже кольца с RFID (NRF) меткой. Некоторые чипы имеют энергонезависимую память небольшого объема.

Прокручиваем решение в голове

Хранить все ключи будем в энергонезависимой памяти микроконтроллера (EEPROM). Хорошее и как мне кажется единственное решение. ATmega328p имеет 1kB памяти, RFID (NRF) ключи содержат уникальный номер размером от 4 до N байт. Если уровнять все типы ключей, то читать мы будем только первый четыре байта любого ключа. Делаем вывод, что 1024/4=256 ключей.

Но необходимо знать точное их количество чтобы не читать пустую память, иначе можно взломать замок так, как это делается с некоторыми домофонами, а именно - передать ключ, состоящий из одних 0 или 255 и Вуаля!

Выделим один и по совместительству первый байт в EEPROM под число с количеством ключей и получаем (1024-1)/4=255,75, а это 255 полноценных четырехбайтных ключей! Это более чем достаточно, хоть домофон делай.

Добавлять ключи будем путем записи его первых четырех байт, в свободную память микроконтроллера, начиная с N+1 занятого байта памяти Arduino. Но делать это необходимо только с использованием мастер ключа. И тут встают два вопроса, какой из ключей должен быть мастером и как его найти среди кучи всех остальных? 

А пусть мастер ключ хранится в строго определенных "ячейках" памяти микроконтроллера, мы ведь делаем это с количеством ключей! Пусть первый байт EEPROM отвечает за количество известных нам ключей, а следующие четыре байта будут отведены мастер ключу. Оставшуюся память отдаем под все остальное.

Открытие дверей. Как понять, что поднесенный к сканеру ключ является тем самым или наоборот. Давайте опять отталкиваться от идеи четырехбайтного ключа. Пройдя все предыдущие этапы записи памяти микроконтроллера, мы с 100% точностью можем сказать, сколько ключей и как они расположены в памяти Arduino. Следовательно, для быстрого сравнения логичнее искать совпадение по первому байту каждого ключа в памяти, и только если оно найдено переходить к сравнению следующего байта. Если какой либо из 4 байт не совпал, то останавливаем проверку и переходить к следующему ключу в памяти микроконтроллера. Если найдено совпадение всех четырех байт ключа, то прекращаем проверку и устанавливаем замок в состояние открыто, иначе оставляем замок запертым.

Пройдя эту не хитрую процедуру, мы можем точно сказать разрешен данному ключу доступ или нет, а заодно является ли он мастер ключом. Ведь мы будем знать номер совпавшего ключа в памяти Arduino следовательно, если он первый (на самом деле нулевой), то это волшебный ключ.

Чтобы добавить новый ключ в память замка необходимо поднести и удерживать у RFID (NRF) сканера мастер ключ. Для начала будет открыта дверь, чтобы впустить владыку в обитель. Но если мастер ключ удерживается в течение 5 секунд (по умолчанию), то замок переводится в постоянно открытое состояние и система распознавания ключей, описанная ранее, начинает работать как проверка наличия ключа в памяти. Ключа нет, тогда записываем его. Ключ найден, значит, ругаемся, плюемся и топаем ногами.

Таким образом, можно добавлять ключи пачками. Для выхода из режима программирования необходимо поднести ключ владыки и удерживает его 5 секунд.

Для очистки памяти решено ввести красную кнопку с защитой от ложного нажатия. Если удерживать её в течение 5 секунд, то контроллер начинает перезапись EEPROM одними нулями. По окончанию операции производится перезапуск программы микроконтроллера. Но теперь нет ни единого ключа, следовательно, и мастера тоже. Непорядок! В этом безмятежном состоянии замок стоит держать постоянно открытым иначе "Се Ля Ви", что в переводе с древнегреческого "не повезло"!

Первый поднесенный к сканеру ключ будет записан в память Arduino и станет мастером т.к его порядковый номер - 0. Следом за этим замок перестанет бездельничать и перейдет в состояние закрыто. С этого момента помещение считается святым, и проникнуть в него может только обладатель единственного записанного в память ключа. Для всех остальных нужно начать процедуру добавления ключей, но её мы уже придумали.

Открытие двери изнутри по нажатию на волшебную кнопку поначалу показалось мне тривиальной задачей, и я добавил этот механизм простым способом (как и кнопку очистки памяти) - ногу микроконтроллера посадил через резистор на землю, а сама кнопка коммутировала на эту же ногу +5V. Все дешево и сердито, но вся соль кроется в деталях! Кнопок открытия двери будет несколько. Все они должны сидеть параллельно и самая дальняя будет расположена как минимум в 15, а то и больше, метрах от контроллера (если мерить длинной кабеля)! Это расстояние смело можно умножать на 2 и получить нереальную для +5V цифру. Необходимо пересадить ногу Arduino с GND на +5V (через резистор 10k), предварительно задействовать встроенный подтягивающий резистор микроконтроллера, а сама кнопка станет прижимать ногу к земле. Резистор между ногой и питанием необходим для избегания короткого замыкания при использовании самой кнопки.

Управление электромагнитом, по понятным и так причинам, происходит через реле. Электромагнит подключен через постоянно замкнутую группу контактов и соответственно при открытии замка, на реле подается логическая единица. Это удобно т.к не приходится постоянно держать реле под напряжением, но есть и обратная сторона медали – если нет питания на контроллере, а на электромагните есть, ты открыть замок станет очень проблемной задачей. В общем, Вам самим выбирать какой из подводных камней оставить. Я свой выбор сделал.

Схема подключения

 - РАСПИНОВКА ----------------------------------------------------------------------------
             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
             Reader/PCD   Uno           Mega      Nano v3    Leonardo/Micro   Pro Micro
 Signal      Pin          Pin           Pin       Pin        Pin              Pin
 -----------------------------------------------------------------------------------------
 RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 SPI SS      SDA(SS)      10            53        D10        10               10
 SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 -----------------------------------------------------------------------------------------

nrf_z1.thumb.jpg.6a5e883f690c1a98aa46f2d

Оставлю дополнительные фото для наглядности. На самом деле, если вы будите внедрять подобное где-то в обиходе, а не просто собирать на столе, то обязательно все документируйте и перепроверяйте по несколько раз.

nrf_z4.jpg.e10319f0e151b93b373b5d3264020

Скрытый текст

nrf_z5.jpg.dd7d824f048ea9148f581fcd6105e

Скрытый текст

nrf_z7.jpg.61ff1243230db1abec9937cf07f2a

Скрытый текст

nrf_z6.thumb.jpg.788a389d2296b0a92e0d42f

Для быстрого монтажа я использовал плату для прототипирования. Цена на неё довольно завышена у братьев Китайцев, но я не пожалел.

nrf_z8.jpg.8c1bb165a20a93c05bd7256c238d7nrf_z10.jpg.cbf31932e4320128c2b2b1e67e15

Перед установкой всего это добра на заслуженное рабочее место проводились испытания различных кабелей для выноса рабочей части (RFID сканера) за стену. В итоге выбор остановился на медной, многожильной витой паре используемой для прокладки Ethernet. Если использовать одножильную (моно) витую пару, то потери могут быть довольно высоки, как и с использованием различных Китайских (якобы медных) шлейфов. Гарантию работы, а соответственно и Вашего попадания в помещение, сможет гарантировать лишь страховой полис.

nrf_z13.jpg.eb0b0efa7831f41b99f63376859f

Сам сканер был помещен в крышку от металлической коробки и закрыт стеклотекстолитом, естественно НЕ фольгированным!

nrf_z12.jpg.103e183dadc201d69b086c47622e

Полученный результат показался мне довольно сносным и был передан заказчику для установки. Для питания был задействован компьютерный блок питания, с которого, через стабилизатор напряжения 12v в 8v для запитки Arduino UNO через стандартный 5,5мм разъем.

Фото монтажа

Скрытый текст

nrf_z16.jpg.4e7d7872ac17c73691753db10fbenrf_z17.jpg.56ede63219cf7012460ac4109997nrf_z19.jpg.b772512ca803a8c3a546ce98d7b8nrf_z21.jpg.87b2f63077553a11f86c4ade8f73nrf_z20.jpg.6594bc4a5c74275e7ba1a82714fa

За дверью все выглядит очень скромно. Т.к. сканер находится в просторном корпусе, то на него была прикреплена кнопка для электронного звонка. Довольно эстетически приятный вид, для промышленного помещения.

Скрытый текст

nrf_z22.thumb.jpg.94a403aacbcf0e1f56b871nrf_z23.thumb.jpg.133042d2c51c011c6df531

Программная часть

Вам понадобятся следующие библиотеки:

  1. RFID-RC522 (NRF)
  2. Bounce2 (избавляет от дребезга контактов при нажатии кнопок)

Хотите помочь проект?

Сам скетч

Скрытый текст

/*
 * Программа управления нагрузкой с помощью RFID-RC522 13.56 мГц
 *  
 * - РАСПИНОВКА ----------------------------------------------------------------------------
 *             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
 *             Reader/PCD   Uno           Mega      Nano v3    Leonardo/Micro   Pro Micro
 * Signal      Pin          Pin           Pin       Pin        Pin              Pin
 * -----------------------------------------------------------------------------------------
 * RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 * SPI SS      SDA(SS)      10            53        D10        10               10
 * SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 * SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 * SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 * -----------------------------------------------------------------------------------------
 *
 */

// Необходимые библиотеки
#include <avr/wdt.h>
#include <EEPROM.h>
#include <SPI.h>
#include <MFRC522.h>
#include <Bounce2.h>

// Необходимые пины
#define PIN_RESET         4        // RESER MEMORY
#define PIN_OPEN          2        // OPEN

#define PIN_RELAY         7        // RELAY
#define PIN_TONE          3        // TONE

#define PIN_RST	          9        // RFID
#define PIN_SS	          10       // RFID

// Инициализация RFID ридера
MFRC522 mfrc522(PIN_SS, PIN_RST);

// Переменные необходимые для работы со списком ключей
byte **keys;
byte keys_count = EEPROM.read(0);

// Переменные необходимые для режима программирования
byte modeProgTime         = 5;       // Количество секунд удержания мастер ключа для входа\выхода в\из режим\а программирования
bool mode                 = false;   // НЕ МЕНЯТЬ!
byte modeClean            = 0;       // НЕ МЕНЯТЬ!
unsigned long modeTimer   = 0;       // НЕ МЕНЯТЬ!
unsigned long resetTimer  = 0;       // НЕ МЕНЯТЬ!

// Управление замком
unsigned long openTimer = 0;

// Защита кнопок от дребезга
Bounce key_reset = Bounce();
Bounce key_open  = Bounce();
/*
  Якобы программный reset, но мы с Вами знаем, что это не так ;)
*/
void(* resetFunc) (void) = 0;
/*
  Функция звукового оповещения.
  Принимает параметры: количество звуковых сигналов, частота в герцах, продолжительность звука, пауза в милесекундах (не обязательно)
*/
void squeaker(byte count, unsigned int Hz, unsigned int duration, unsigned int sleep = 0) 
{
  for(int i=0; i<count; i++) {
    tone(PIN_TONE, Hz, duration);
    if(sleep > 0) delay(sleep);
  }
}
/*
  Функция читает EEPROM и составляет список активных ключей
  Первый байт в памяти содержит количество ключей
  UID ключа содержит 4 байта
  Общая память 1 + количество ключей * 4
  Максимум можно записать 255 ключей
*/
void keysRead() {
  // Выводим количество ключей
  Serial.print(F("KEYS COUNT: "));
  Serial.println(keys_count);
  int eb = 0;
  keys = (byte**)malloc(sizeof(byte*)*keys_count);
  // Читаем список ключей из EPROM
  Serial.println(F("------------------------------"));
  for(byte i=0; i<keys_count; i++) {
    Serial.print(F("KEY: "));Serial.print(i);Serial.print(" | ");
    keys[i] = (byte*)malloc(sizeof(byte)*4);
    for(byte b=0; b<4; b++) {
      keys[i][b] = EEPROM.read(++eb);
      Serial.print(keys[i][b]);
      if(b < 3) Serial.print(F(" "));
    }
    Serial.println();
  }
  Serial.println(F("------------------------------"));
  Serial.println();
}
/*
  Функция выводит UID ключа и, при необходимости, сопроводительное сообщение
*/
void uidPrint(String text = "") {
  Serial.print(F("UID: "));
  for(byte i=0; i<mfrc522.uid.size; i++) {
    Serial.print(mfrc522.uid.uidByte[i]);
    if(i < mfrc522.uid.size - 1) Serial.print(F(" "));
  }
  Serial.println();
  if(text.length() != 0) Serial.println(text + "\r\n");
}
/*
  Инициализация программы
*/
void setup() {
  // Настраиваем сторожевой таймер
  wdt_disable();
  delay(8000); // <- ЗАКОМЕНТИРУЙТЕ ЭТУ СТРОКУ, ЕСЛИ ЗНАЕТЕ, ЧТО ТАКОЕ wdt_enable(WDTO_8S);
  wdt_enable(WDTO_8S);
    
  // Инициализация используемых пинов
  // Реле
  pinMode(PIN_RELAY, OUTPUT);
  digitalWrite(PIN_RELAY, HIGH);
  
  // Кнопка сброса памяти
  pinMode(PIN_RESET,INPUT_PULLUP);
  key_reset.attach(PIN_RESET);
  key_reset.interval(5);
  
  // Кнопка открытия двери
  pinMode(PIN_OPEN,INPUT_PULLUP);
  key_open.attach(PIN_OPEN);
  key_open.interval(5);   
  
  // Инициализация консоли
  Serial.begin(9600);
  while (!Serial);
  // Приглашаем в гости
  Serial.println(F("iT4iT CLUB (C) 2015\r\nhttps://it4it.club\r\n"));
  
  // Инициализация ридера
  SPI.begin();
  mfrc522.PCD_Init();

  // Читаем количество ключей
  // Значение должно быть равным или больше 1 т.к первый ключ это мастер
  // В случае утери мастер ключа, мы можем сбросить EEPROM и прикрутить новый мастер ключ
  if(keys_count > 0 and keys_count < 255) {
    keysRead();
  }
  else {
    keys_count = 0;
    Serial.println(F("The master key is not in memory. The first presentation to the key will be the master!\r\n"));
    digitalWrite(PIN_RELAY, LOW);
  }
}
/*
  Лупаем, что происходит
*/
void loop() {
  // Сбрасываем сторожевой таймер микроконтроллера
  wdt_reset();

  if(resetTimer > millis()+10000) resetTimer = 0;
  if(openTimer > millis()+10000) openTimer = 0;
  
  // Очистка памяти
  key_reset.update();
  if(key_reset.read() == HIGH) {
    if(resetTimer == 0) resetTimer = millis();
    else {
      if((millis()-resetTimer)/1000 > 5) {
        Serial.println(F("Launched memory cleaning"));
        squeaker(4, 1600, 300, 200);
        wdt_disable();
        for(int i=1; i<=EEPROM.length(); i++) {
          EEPROM.write(i, 0);
          if(!(i%50)) Serial.println(F("#")); else Serial.print(F("#"));
        }
        Serial.println(F("\r\nMemory cleaning is completed\r\n"));
        delay(1000);
        resetFunc();
      }
    }
  }
  else if(resetTimer != 0) resetTimer = 0;
  // Открытие двери с кнопки
  key_open.update();
  //Serial.println(key_open.read());
  if(key_open.read() == LOW and openTimer == 0) {
    if(keys_count > 0) {
      openTimer = millis()/1000;
      digitalWrite(PIN_RELAY, LOW);
      Serial.println(F("The door opened from the inside"));
      squeaker(5, 3200, 100, 300);
    }
    else Serial.println(F("\nAttempting to open the lock from the inside.\nAccess denied. The master key is unknown. The castle is always open.\n"));
    delay(2000);
  }  
  // Автоматическое закрытие двери
  if(openTimer != 0) {
    if(millis()/1000 - openTimer > 5) {
      openTimer = 0;
      digitalWrite(PIN_RELAY, HIGH);
      Serial.println("* closed lock\r\n");
    }
  }
  // Если ключ отсутствует или не читается, не выполняем дальнейший код
  if(!mfrc522.PICC_IsNewCardPresent()) {
    // Очистка таймера входа в режим программирования, в случае если ридер свободен
    if(modeTimer != 0) {
      if(++modeClean > 5) modeTimer = modeClean = 0;
    }
    return;
  }
  if(!mfrc522.PICC_ReadCardSerial()) return;
  // Останавливаем режим очистки
  modeClean = 0;
  
  // Кривое создание мастер ключа
  if(keys_count == 0) {
    for(byte i=0; i<4; i++) EEPROM.write(i+1, mfrc522.uid.uidByte[i]);
    EEPROM.write(0, keys_count = 1);
    uidPrint(F("master key is created"));
    digitalWrite(PIN_RELAY, HIGH);
    keysRead();
    squeaker(8, 1200, 100, 100);
    delay(2000);
    return;
  }
  
  // Проверка ключа на соответствие
  bool access = false;
  bool master = false;
  for(byte i=0; i<keys_count; i++) {
    for(byte b=0; b<4; b++) {
      if(keys[i][b] != mfrc522.uid.uidByte[b]) break;
      if(b == 3) {
        access = true;
        if(i == 0) master = true;
        // Останавливаем проверку костылем т.к "break 2;" не работает
        i = keys_count;
      }
    }
  }
  // ========================================================================================
  // Следующая секция имеет двойное назначение:
  //  1. В обычном режиме - контроль доступа
  //  2. В режиме программирования - запись ключа при его отсутствие в EEPROM
  // ========================================================================================
  // ОБЫЧНЫЙ РЕЖИМ
  // ========================================================================================
  if(access and !mode and !master) {
    // Доступ разрешен
    openTimer = millis()/1000;
    digitalWrite(PIN_RELAY, LOW);
    uidPrint(F("access allow"));
    squeaker(2, 2200, 200, 200);
	delay(2000);
  }
  else if(!access and !mode and !master) {
    // Доступ запрещен
    uidPrint(F("access dany"));
    squeaker(1, 500, 1000);
    delay(2000);
  }
  // ========================================================================================
  // РЕЖИМ ПРОГРАММИРОВАНИЯ
  // ========================================================================================
  else if(access and mode and !master) {
    // Попытка записи существующего ключа
    uidPrint(F("error: key elrady exists in eeprom"));
    squeaker(2, 500, 300);
    delay(2000);
  }
  else if(!access and mode and !master) {
    // Записываем новый ключ
    // Максимум 255 ключей (с учетом первого байта) для 328 камня
    if(keys_count < 255) {
      for(byte i=0; i<4; i++) EEPROM.write(1 + keys_count*4 + i, mfrc522.uid.uidByte[i]);
      EEPROM.write(0, ++keys_count);
      uidPrint(F("add key in eeprom"));
      keysRead();
      squeaker(2, 2200, 200, 200);      
    }
    else {
      uidPrint(F("error: not enough memory for recording key!"));
      squeaker(2, 500, 300);
    }
    delay(2000);
  }
  // ========================================================================================
  // РАБОТА С МАСТЕР КЛЮЧОМ
  // ========================================================================================
  else if(access and master) {
    // Мастер ключ в обычном режиме
    if(modeTimer == 0) {
      modeTimer = millis()/1000;
      if(!mode) {
        openTimer = millis()/1000;
        digitalWrite(PIN_RELAY, LOW);
        // Сигнал о наличии мастер ключа в обычном режиме
        uidPrint(F("MASTER KEY"));
        squeaker(2, 2200, 200, 200);
      }
    }
    else {
      if(millis()/1000 - modeTimer > modeProgTime and modeTimer != 0) {
        modeTimer = 0;
        if((mode = !mode) == true) {
          // Вход в режим программирования
          digitalWrite(PIN_RELAY, LOW);
          uidPrint(F("MASTER PROGRAMMING MODE ON"));
          squeaker(4, 1200, 200, 200);
        }
        else {
          // Выход из режима программирования
          digitalWrite(PIN_RELAY, HIGH);
          uidPrint(F("MASTER PROGRAMMING MODE OFF"));
          squeaker(4, 2200, 200, 200);
        }
      }
      delay(2000);
      // Дополнительные действия по таймеру modeProgTime
    }
    // Мастер ключ удерживается у ридера
  }
}

 

Все довольно плотно закомментировано, но есть большой фронт для доработки. Надеюсь, Вы дополните это создание своими идеями и нужным функционалом, а я в свою очередь уже присмотрел идеи для будущего обновления и заказал новый модуль RC522 для теста.

PS: Желаю Вам приятного и надеюсь полезного использования.

  • Like 3

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Приветствую Kitsum!
Очень понравился Ваш проект, много чего перепробовал!
Собрал на макетке, всё работает как часики!!! Хотелось бы добавить некоторые плюшки к данному девайсу, но сам я не кодер и даже если начать, уйдёт ума времени и не факт, что работать будет правильно! Вы не могли бы помочь?

В 24.11.2015в02:58, Kitsum сказал:

а я в свою очередь уже присмотрел идеи для будущего обновления и заказал новый модуль RC522 для теста.

 

А скоро будет новый проект?!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Скрытый текст
4 часа назад, Alex13 сказал:

Приветствую Kitsum!
Очень понравился Ваш проект, много чего перепробовал!
Собрал на макетке, всё работает как часики!!! Хотелось бы добавить некоторые плюшки к данному девайсу, но сам я не кодер и даже если начать, уйдёт ума времени и не факт, что работать будет правильно! Вы не могли бы помочь?

А скоро будет новый проект?!

 

Привет Alex13!

Конечно, постараюсь помочь в меру своих возможностей и свободного времени. Новые идеи хотел реализовать после обновления проекта с метеостанцией, но возможно все еще изменится. В общем, ждем Ваших идей и предложений!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо, что не отказали, а там уж как получится!
Есть желание приспособить этот замок ещё и в качестве охраны помещения и добавить немного универсальности!

Собственно идеи:                                                                                                                                                                                                                                      1. Возможно ли добавить переключатель или сделать, чтобы функция программировалась с помощью мастер-ключа, отключение автоматического        
     закрытия?(т.е. чтобы можно было выбирать в каком режиме работает замок, с автоматическим закрытием или поднесли ключ, замок открылся, ещё раз      
     поднесли - закрылся).
2. Добавить парочку выходов на включение внешних устройств(например сирена, свет ну ли чего ещё неважно), при попытки подбора ключа(поднесли три раза  
    не прописанный ключ)?
    Функционал выходов:
    На одном, логический уровень держится минуту и снимается, до следующее попытки подбора ключа, а на втором держится до открытия замка прописанным ключом.
3. Ну и в идеале хотелось бы иметь вход для внешнего "раздражителя" выходов на включение внешних устройств, с тем же функционал выходов!
    Т.е. появился логический уровень на этом входе и запускается процесс функционала выходов.

Если написал чушь, сильно не пинайте, если есть возможность разъясните, идеи идеями, но наверное не всё можно реализовать?!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Первые два пункта мне понятны и их можно реализовать.

С третьим немного не ясно. Имеется в виду, что при появлении логической единицы на входе, задействуются выхода описанные во втором пункте? Если так, то и этот вариант можно реализовать, иначе прошу пояснений.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Да, Вы правильно поняли, при появлении логической единицы на дополнительном входе

Цитата

задействуются выхода описанные во втором пункте

с тем же функционалом выходов.

Под внешним раздражителем, я подразумевал сигнал с датчика(движения, удара, лазерный барьер ну и т.д.)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Скрытый текст

Снимок экрана из 2016-01-21 18:56:25.png

аааа только купил ардуину аааа этот проэкт понравился аааааааа что это у меня компилятор пишет ? почему ?

 

 

Изменено пользователем svchekalin

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Скрытый текст
1 час назад, svchekalin сказал:

аааа только купил ардуину аааа этот проэкт понравился аааааааа что это у меня компилятор пишет ? почему ?

 

Пожалуйста, не нужно таким способом выражать эмоции, прошу Вас держать форум в чистоте.

Предлагаю Вам использовать последнюю версию Arduino IDE:

  1. arduino.cc
  2. arduino.org

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

прошу прощения ! Очень железо понравилось а тут еще и код такой интересный ! но к сожелению более старшая версия проблем не решает

Скрытый текст

56a124104fd65_2016-01-21212919.thumb.png

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

to svchekalin

В силу конфликта между Arduino LLC и Arduino SRL, они ведут разработку двух IDE отличных друг от друга, соответственно и библиотеки имеют различия. При изучение библиотеки EEPROM от arduino.org мне не удалось найти метод length. Уверен, что это все объясняет. Я использовал среду разработки от arduino.cc

До сегодняшнего дня я знал, что эти ребята имеют отличия в IDE, но не думал что все так серьезно! С другой стороны я рад, что эта проблема всплыла т.к заставила меня начать изучать библиотеки от arduino.org

Для решения проблемы, воспользуйтесь средой от arduino.cc или попробуйте заменить встроенные библиотеки, но последний вариант может привести к другим подводным камням!

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Извините не нашел как отредактировать предыдущее сообщение . В общем теперь вот :

Скрытый текст

56a1d25f55e31_2016-01-22095055.thumb.png

 

я даже грешным делом жава машину установил но нет увы...

Изменено пользователем svchekalin

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Скрытый текст
2 часа назад, svchekalin сказал:

Извините не нашел как отредактировать предыдущее сообщение . В общем теперь вот :

  Скрыть содержимое

56a1d25f55e31_2016-01-22095055.thumb.png

 

я даже грешным делом жава машину установил но нет увы...

 

Действительно проблема существует и появляется в разных версиях IDE

processing.app.debug.RunnerException
   at cc.arduino.packages.uploaders.SerialUploader.uploadUsingPreferences(SerialUploader.java:179)
   at cc.arduino.UploaderUtils.upload(UploaderUtils.java:78)
   at processing.app.Sketch.upload(Sketch.java:1178)
   at processing.app.Sketch.exportApplet(Sketch.java:1152)
   at processing.app.Sketch.exportApplet(Sketch.java:1124)
   at processing.app.Editor$DefaultExportHandler.run(Editor.java:2417)
   at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
   at java.lang.String.replace(String.java:2240)
   at processing.app.helpers.StringReplacer.replaceFromMapping(StringReplacer.java:97)
   at processing.app.helpers.StringReplacer.replaceFromMapping(StringReplacer.java:89)
   at processing.app.helpers.StringReplacer.formatAndSplit(StringReplacer.java:37)
   at cc.arduino.packages.uploaders.SerialUploader.uploadUsingPreferences(SerialUploader.java:174)
   ... 6 more

Я вижу, что у Вас используется версия 1.6.8 от обновления 21.01.2016 18:15:49 GMT. А уже 22.01.2016 официальный сайт ссылается на предыдущую версию 1.6.7. На странице Arduino Software Release Notes последнее обновление идет от 17.12.2015, что актуально опять же для версии 1.6.7

Я делаю вывод, что был не удачный день и в сеть ушла недоработанная версия, после выявления данной проблемы разработчик оперативно откатился на предыдущую версию. Вы в свою очередь стали тем счастливчиком, кто успел скачать ...

Предлагаю в очередной раз переустановить Arduino IDE и воспользоваться предыдущей версией. У меня установлена версия 1.6.6, но я проверил и текущую 1.6.7, все работает. https://www.arduino.cc/en/Main/OldSoftwareReleases#previous 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо огромное ! Вы обсалютно правы , у меня все заработало (1.6.7) сс 

вобщем рфид считыватель подключен к уно и больше ничего нет ,судя по данным компорта происходит постоянная перезапись памяти ключей видимо в силу отсутствия той самой кнопки . вопрос : как отключить сие действие ?

                                                                                                                             а так я счаслив и попискиваю как обожранный хомяк

Изменено пользователем svchekalin

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Скрытый текст
4 часа назад, svchekalin сказал:

Спасибо огромное ! Вы обсалютно правы , у меня все заработало (1.6.7) сс 

вобщем рфид считыватель подключен к уно и больше ничего нет ,судя по данным компорта происходит постоянная перезапись памяти ключей видимо в силу отсутствия той самой кнопки . вопрос : как отключить сие действие ?

                                                                                                                             а так я счаслив и попискиваю как обожранный хомяк

Необходимо кинуть перемычку между пинами D4 и GND, в противном случае контроллер думает, что подается команда на очистку EEPROM.

Пин D2 подтягивается к +5v, при подтяжке к земле произойдет открытие замка. На земле эта кнопка завязана на случай если дистанция от контроллера до кнопки открытия замка будет исчисляться в десятках метров. 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

с перемычкой разобрался ... а реально прикрутить изернет шилд чтобы с бд подружить ?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Скрытый текст
1 час назад, svchekalin сказал:

с перемычкой разобрался ... а реально прикрутить изернет шилд чтобы с бд подружить ?

Если получится разнести с общих пинов Ethernet и RFID, то вполне реально. На сколько жизнеспособно это другой вопрос.

Если предположить, что физически все срослось, то сходу в голову приходят мысли:

  1. Как использовать Ethernet? срастить с базой людей и организовать мини проходную с турникетом или слать статистику открытия на сервер или еще что-то?
  2. Что делать если Ping поднимется до критической отметки?
  3. Что делать если нет сети, например потух маршрутизатор? впускать по заранее записанным ключам в EEPROM или блокировать все?
  4. Как при таких малых мощностях защитить трафик в сети (пересылку ключей и иной закрытой информации)?

Это то, что пришло в голову сразу, естественно список в разы увеличится если начать это делать. Хотя скажу честно, что ради спортивного интереса можно попробовать, но это делать нужно на ATmega 2560 или лучше разделить обязанности между двумя ATmega 328 связанными между собой каким ни-ть интерфейсом.

PS: намного проще и реальнее это реализовать на Raspberry PI, Orange PI или на другой плате с аналогичными возможностями. Сразу в голове крутится RC522 + Ethernet + SQL + SSH + Apache + ... 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Вот тут буржуи чтото подобное начали ваять да тема у них заглохла. Дело в том что я люблю всякий хайтек и хотел сам собрать систему доступа а такк как квартира сдается то с электронным замком многие проблемы снимаются. думаю базу на роутере поднять а безопасность потому и по витухе а не по вайфаю.

Скрытый текст


/**
* Читайте карту, используя читателя mfrc522 на интерфейсе SPI
*Расположение контактов должен быть следующим (на Arduino Uno):
* MOSI: Pin 11 / ICSP-4
* MISO: Pin 12 / ICSP-1
* SCK: Pin 13 / ISCP-3
* SS: Pin 7
* RST: Pin 9
*/
#include <SPI.h>
#include <RFID.h>
#include <Ethernet.h>

#define SS_PIN 7
#define RST_PIN 9


// Только для Mac каждый EthernetShield (необходимо изменить)
byte mac[] = { 0x90,0xA2,0xDA,0x0F,0x49,0x90 };

// Descomentar esta linea si se desea un IP fijo
IPAddress ip(192,168,2,50);

// Descomentar esta linea para asignar un DNS fijo
//IPAddress myDns(192,168,1,1);

// Inicializa la instancia client
EthernetClient client;

// Direccion del servidor
char server[] = "http://localhost/dir1/";


RFID rfid(SS_PIN, RST_PIN);

// Setup variables:
    int serNum0;
    int serNum1;
    int serNum2;
    int serNum3;
    int serNum4;
    int finalnumber;

void setup()
{
  Serial.begin(9600);
// disable SD SPI
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);

  // disable w5100 SPI
  pinMode(10, OUTPUT);
  digitalWrite(10, HIGH);
 
  // Espera 1 segundo para que se inicie la tarjeta Ethernet
  delay(1000);
  // Si deseas usar un ip fijo y un DNS fijo descomentar esta linea y comentar la linea 39
  // Ethernet.begin(mac, ip, myDns);
  // Inicializa la tarjeta ethernet mediante DHCP
  EthernetServer server(80);
  Ethernet.begin(mac,ip);
  server.begin();
  // Imprime la direccion IP de la tarjeta
  Serial.print("Direccion IP: ");
  Serial.println(Ethernet.localIP());
 
  SPI.begin();
  rfid.init();
 
}

void loop()
{
    
    if (rfid.isCard()) {
        if (rfid.readCardSerial()) {
            if (rfid.serNum[0] != serNum0
                && rfid.serNum[1] != serNum1
                && rfid.serNum[2] != serNum2
                && rfid.serNum[3] != serNum3
                && rfid.serNum[4] != serNum4
            ) {
                /* With a new cardnumber, show it.*/
                Serial.println(" ");
                //Serial.println("Card found");
                serNum0 = rfid.serNum[0];
                serNum1 = rfid.serNum[1];
                serNum2 = rfid.serNum[2];
                serNum3 = rfid.serNum[3];
                serNum4 = rfid.serNum[4];
                finalnumber= 1;
              delay(1000);
              if (client.connect(server,80)) {

                    // Envia el requerimiento al servidor via GET
                    Serial.println("Iniciando conexion...");
                    client.print("GET /obtienevalor.php?idcard=finalnumber");
                    //client.print(tempC);
                    client.println(" HTTP/1.1");
                    client.print("Host: ");
                    client.println(server);
                    client.println("User-Agent: Arduino-Ethernet");
                    client.println("Connection: close");
                    client.println();
                  }
                  else {
                    // Si la conexion fallo se desconecta
                    Serial.println("Error al conectarse al servidor");
                    Serial.println("Desconectando...");
                    client.stop();
                  }             
              

             } else {
               /* If we have the same ID, just write a dot. */
               Serial.print(".");
             }
          }
    }
    
    rfid.halt();
}

 

физика работает я проверил

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

|Вот  еще библеотеки нашел интересные https://github.com/ChuckBell/MySQL_Connector_Arduino

мне видится задача так : роутер - apache mysql php. ардуинка осуществляет контроль ключей и ред врайт базы , плюс без перебойник.секъюрность не нужна по причине того что данные идут по медяхе внутри помещения кстати тут прочитал что ардуинкой можно через порт управлять ,еще одно приятное открытие ))

Изменено пользователем svchekalin
  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Огромное, человеческое СПАСИБО Вам за труды!!! Внимательно всё изучу, разберусь о результатах обязательно отпишусь! Замечательный проект!!!

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

До собрал, пока на макетке для теста, всё работает как часики, ну и конечно же появились вопросы! :)

Цитата

Естественно логические уровни на пинах A1 и A2 можно инвертировать в программе.

Подскажите как? 

 

Цитата
В 21.01.2016в15:15, Alex13 сказал:

Да, Вы правильно поняли, при появлении логической единицы на дополнительном входе

задействуются выхода описанные во втором пункте

с тем же функционалом выходов.

Под внешним раздражителем, я подразумевал сигнал с датчика(движения, удара, лазерный барьер ну и т.д.)

Что-то я не понял, где этот вход?

И ещё, можно ли сделать чтобы этот вход активировался только когда замок закрыт?

Не сочтите за наглость!!!:$

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

to Alex13

Обновил код в посте выше. Но проверить не могу т.к нахожусь на работе, предлагаю это сделать Вам или дождаться пока я приеду домой.

Чтобы упростить работу, добавил параметр в конфигурацию (57 строка)

// Переменные для сигнализации
bool invert = true;  // Инвертировать выходы сигнализации (A1, A2)

Измените значение на false и на этих входах должен начать появляться высокий уровень при тревоге.

19 час назад, Alex13 сказал:

Что-то я не понял, где этот вход?

И ещё, можно ли сделать чтобы этот вход активировался только когда замок закрыт?

Не сочтите за наглость!!!

Это пин A0, как с ним работать описано в п3. поста со скетчем.

По поводу активации. Добавил отслеживание как состояние пина A0, так и текущее состояние реле замка. Если сигнализация включена и замок закрыт, то тревоги будут считаться, в противном случае счетчик попросту не инкрементируется.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

С появлением высокого уровня порядок, но пока ещё буду тестить!

А вот с пином A0(я так понимаю это активация или отключение режима сигнализации).......... я наверное объяснять толком не умею,:$ попробую нарисовать может тогда получится объяснить чего хочу. 1.thumb.jpg.a9ebb5b45f1fb9e326898c419120

.............. и запускаются(только когда замок закрыт) выходы с уже известным алгоритмом работы!
 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Пожалуйста, войдите для комментирования

Вы сможете оставить комментарий после входа



Войти сейчас

  • Похожие публикации

    • Автор: Kitsum
      Привет друзья.
      В данной теме пойдет речь о конфигурации микроконтроллера через UART (Universal Asynchronous Receiver-Transmitter) интерфейс. А рассмотрим мы это на примере MQTT логгера. В данном случае, это будет логгер температуры. Мне это устройство потребовалось на работе, даже не мне, а моим коллегам, и оно действительно работает и приносит огромную пользу т.к контроль температуры производится совместно с отличной, на мой взгляд, системой мониторинга Zabbix с оперативными оповещениями, построением графиков, блэк-джеком и... Подробнее о дружбе Arduino и Zabbix можно почитать тут
      Но как всегда, есть нюансы. А заключаются они в том, что в будущем, обслуживать армию мелких контроллеров придется людям, которые заняты своими задачами и им попросту некогда изучать Arduino, не говоря уже о серьезных альтернативах, разбираться в том, как прописать нужные значения переменных в программу и загрузить её в микроконтроллер. Все настройки необходимо производить быстро, с явным указанием изменяемого параметра и его значения. Ровно также, как это делается с любым промышленным оборудованием.
      И тут на помощь приходит UART
      Микросхема UART to USB имеется в большинстве плат семейства Arduino, а там, где её нет, обычно выведены соответствующие "пины". И все это очень облегчает жизнь т.к позволяет общаться с контроллером, просто подключив его к компьютеру напрямую или через переходник, благо их везде навалом, да и стоят они как пачка семечек. Остается только запустить любой терминал, который умеет доставлять в конец строки символ "перевод строки", что известен в народе как "\n", а в ASCII таблице имеет номер 0A.
      Кстати, в Serial мониторе Arduino IDE выставить символ конца строки можно так

      Ну а дальше только, что и остается, как общаться с устройством на той стороне. И тут мы переходим к основному алгоритму программы. Но перед этим хочу отметить, и это ВАЖНО, что за любое упрощение жизни, всякие красивости и прочее, приходиться платить, и цена довольно высока! В данном случае, это ОЗУ микроконтроллера. Поэтому не раскатываем губы, а если очень хочется, то берем следующий по характеристикам микроконтроллер. А начинать мы будем с ATmega328P, что известен в народе как Arduino UNO, Arduino Nano, IBoard v1.1 и т.д по списку. Заканчивать Вы можете чем угодно, хоть ATmega2560, ESP8266 или ESP32. В противном случае, производим оптимизацию кода, отказываемся от громоздких библиотек, или вообще, от Arduino IDE.
      Что мы хотим получить
      Вся конфигурация микроконтроллера должна храниться в энергонезависимой памяти (ПЗУ) известной нам как EEPROMM. Если в ПЗУ конфигурация отсутствует, необходимо иметь резервный план. И им станет сброс конфигурации на настройки по умолчанию. Это поведение знакомо всем, особенно по домашним дешевым маршрутизаторам, а значит, интуитивно понятно. Выводить справку при начале общения пользователя и устройства, на мой взгляд, как манеры высшего общества. Контроллер должен представляться и сообщать всю необходимую информацию о себе и о том, как с ним вести диалог. Все команды должны быть просты и иметь не двусмысленное значение. И конечно, мы должны иметь возможность просмотра текущего состояния датчиков или процессов, которыми занимается устройство в свободное от общения с нами время. Как сохранять конфигурацию в EEPROM
      Пожалуй, стоит начать с того, как сохранить конфигурацию микроконтроллера в энергонезависимую память. Для этих целей, в стандартный набор инструментов Arduino IDE входит библиотека для работы с EEPROM.
      #include <EEPROM.h> На данный момент нас интересуют две функции, это чтение и запись
      EEPROM.get(address, variable); EEPROM.put(address, variable); Обе принимают два параметра:
      Адрес, начиная с которого будет произведено чтение или запись данных в память Переменная чье содержимое надо сохранить или в которую нужно из памяти прочитать Особенность работы этих функция заключается в том, что в зависимости от типа переданной им переменной во втором параметре, будет произведено чтение или запись ровно того количества данных которое соответствует размеру типа этой самой переменной. На простом языке это означает, что если переменная variable будет иметь типа byte, то и работать мы будем с объемом памяти в 1 байт. И тоже самое произойдет с абсолютно любым типом данных пока мы не упремся в размеры самого EEPROM или ОЗУ микроконтролера. Из этого всего следует, что мы можем создать свой собственный тип данных, разместить в нем необходимую нам информацию и всего лишь двумя функциями помещать его в память и извлекать обратно.
      И в этом нам поможет пользовательский составной тип - структура (struct). Данный тип позволяет объединить в себе различные типы данных, упорядочить их и присвоить им понятные имена.
      Это общий пример для большего понимания, как объединить несколько типов данных в одной структуре, получить к ним доступ, записать и прочитать их из EEPROM.
      Наша структура будет немного сложнее, но суть остается той же самой.
      // Дополнительная структура описывающая IPv4 адреса struct addres { byte a; byte b; byte c; byte d; }; // Структура объекта конфига для хранения в EEPROM struct configObj { addres ip; addres subnet; addres gateway; addres dns; byte mac[6]; byte hex; char server[40]; char topic[40]; } config; Данная структура хранит сетевые настройки для работы с Ethernet модулем (w5100 и выше) Arduino, базовые настройки для связи с MQTT брокером. Сразу при описании структуры мы объявили новую переменную с именем config с типом нашей структуры.
      ВАЖНО: кроме наших данных в структуре имеется дополнительная переменная с именем hex. Её задача, это контроль наличия наших данных в EEPROM. Она всегда должна содержать одно и тоже значение. Представьте ситуацию, что вы взяли контроллер в EEPROM которого находится какая-либо информация (может там чисто, но мы этого не знаем наверняка) и мы прочитаем данные и поместим их в нашу переменную. В итоге мы получим данные которым нельзя доверять, а что еще хуже, это если эти самые данные нарушат работу внешнего оборудования.
      Более правильным, на мой взгляд, будет проверка значений по конкретно определенным адресам. Например, мы знаем, что в 16 байте должно быть значение 0xAA и если оно действительно там, то мы убеждаемся, что это наша информация. Естественно, что контрольных точек может быть несколько и разумеется с разными значениями, это увеличит гарантию того, что данные являются нашими, но 100% гарантии не даст. Для более серьезных проектов есть более серьезные методы, например, подсчет контрольной суммы всего набора данных.
      Также структура может иметь вложенные структуры, у нас ими являются: ip, subnet, gateway, dns. Вы можете отказаться от такого варианта и записывать данные просто в массив байт, как это было сделано с MAC адресом. Естественно, что обращаться к этим полям нужно по-разному.
      Запись данных в поле subnet
      config.subnet = {255, 255, 255, 0}; Запись данных в поле mac
      byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02}; memcpy(config.mac, mac, 6); С записью данных в поле server все еще проще
      config.server = "mqtt.it4it.club"; Функция, которая возвращает нашу структуру данных с полностью заполненными полями.
      // Начальный конфиг configObj defaultConfig() { configObj config = { {192, 168, 0, 200}, {255, 255, 255, 0}, {192, 168, 0, 1}, {192, 168, 0, 1}, {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02}, 0xAA, // Не трогать! Используется для проверки конфигурации в EEPROM F("mqtt.it4it.club"), F("arduino/serial/config") }; return config; } К примеру, два последних значения записывать не обязательно, тогда эти поля останутся пусты если использовать данную функцию для возврата к "заводским" настройкам.
      Вот пример того, как используя описанную нами структуру, мы проверяем целостность настроек в EEPROM и в случае не совпадения hex значений, загружаем настройки по умолчанию.
      const byte startingAddress = 9; bool configured = false; void loadConfig() { EEPROM.get(startingAddress, config); if (config.hex == 170) configured = true; else config = defaultConfig(); configEthernet(); // Функция производящая настройку сети } Как контроллеру начать понимать, что от него хотят
      В Arduino имеется функция, вызываемая каждый раз, когда в передаваемый буфер данных попадает знакомый нам символ перевода строки.
      void serialEvent() { // Вызывается каждый раз, когда что-то прилетает по UART // Данные передаются посимвольно. Если в строке 100 символов, то функция будет вызвана 100 раз } И в контексте обсуждаемой нами программы, мы можем представить ее в следующем виде
      void serialEvent() {   serialEventTime = millis();   if (console.available()) {     char c = (char)console.read();     if (inputCommands.length() < inputCommandsLength) {       if (c != '\n') inputCommands += c;       else if (inputCommands.length()) inputCommandsComplete = true;     }   } } Её задача, символ за символом, собрать в кучу все переданные нами данные и при получении заветного символа перевода строки (именно он даст нам понять, что передача сообщения завершена) сообщить, что команда получена и передать накопленный буфер данных своей напарнице по цеху.
      Но перед тем как это сделать стоит рассмотреть альтернативную ветку развития событий, а именно тот факт, что нам попросту могут прислать огромную, кучу шлака без волшебного символа, а раз могут, значит рано или поздно пришлют. И мы бесполезно потратим ценные ресурсы микроконтроллера, что может привести к непредсказуемым результатам в дальнейшем. Поэтому, в логику функции, мы добавим дополнительное ограничение на количество переданных символов, если оно достигнуто, то попросту перестаем воспринимать последующие данные.
      Останется только избавиться от них, и самым удобным моментом будет, когда этот поток шлака прекратиться. Чтобы об этом узнать мы будем запоминать время, когда пришел каждый из символов переданной строки перезаписывая соответствующую временную переменную данными о следующем символе и т.д пока поток не иссякнет. И как только расхождение текущего времени CPU и времени, когда поступил последний символ превысит некоторое значение, пусть это будет 1 секунда, мы очистим нашу память. Этот простой механизм напоминающий амнезию позволит избавить нас от лишних проблем.
      Переменная отвечающая за размер принимаемого буфера
      const byte inputCommandsLength = 60; Теперь можно переходить к напарнице предыдущей героини и, по совместительству, основной рабочей лошадки - функции обрабатывающей адекватно полученные данные.
      void serialEventHandler() { // вызывается в loop и проверяет взведена ли переменная inputCommandsComplete // в полученных данных пытается распознать команды } По началу я хотел описать данную функцию в упрощенном варианте, но в процессе понял, что ничего хорошего из этого не выйдет, и я решил описать только ключевые моменты, но их будет достаточно, на мой взгляд.
      Разбор serialEventHandler
      Полученные данные будут переданы нам в переменной inputCommands с типом String
      В первую очередь стоит почистить ее от лишних пробельных символов. Они часто встречаются в начале и в конце строки если пользователь копирует текст, а не набирает его самостоятельно. Это распространенная ситуация, приводящая к отказу принятия команды и бороться с ней очень просто.
      inputCommands.trim(); Далее стоит отсеять команды, не несущие никакой динамической информации, например, help, restart, reset и т.п это предписывающие команды которые заставляют контроллер выполнять строго описанные функции без вмешательства в их работу.
      if (inputCommands == F("help")) { consoleHelp(); } else if (inputCommands == F("restart")) { resetFunc(); } else { // Все сложные команды обрабатываются в этом блоке } Как Вы видите, все очень просто и скучно. Но не в том случае если команда динамическая, то есть содержит не только саму команду (заголовок) но и полезную нагрузку (параметр) которая может меняться раз от раза. Простой пример это команда изменения ip адреса и её варианты:
      ip 37.140.198.90 ip 192.168.0.244 ip 10.10.10.88 В данном случае, нам стоит понять, относится ли данная команда именно к ip адресу. Для этого в наборе String имеется отличный метод, позволяющий производить сравнение переданного ему параметра с началом строки.
      if (inputCommands.startsWith(F("ip"))) { // Строка inputCommands начинается с пары символов "ip" } Если все идет так, как мы задумали, то нам стоит отделить динамическую часть - наш параметр, от заголовка и получить полезную нагрузку. В этом нам поможет, опять же из набора String, метод substring позволяющий получать часть строки с указанием начального и конечного символа подстроки. Последний параметр указывать не обязательно и в таком случае мы получим всю строку начиная с указанного символа.
      inputCommands.substring(4) В данном случае начиная с 4-его и заканчивая последним. И как Вы успели заметить, отсчет мы начинаем не с третьего символа, что соответствует нашей строке без вступительного "ip", а на один больше т.к между заголовком и параметром имеется разделяющий символ в виде пробела.
      Далее, полученную строку мы передадим в функцию, занимающуюся разбором на компоненты и принимающую следующие параметры:
      Указатель на переменную с типом char, для этого нам потребуется преобразовать наш тип String Символ разделителя, что для IPv4 является точка "." Указатель на массив типа byte, которому будет присвоен результат разбора Количество искомых элементов в строке И система счисления, подразумеваемая в качестве исходной для записи элементов подстроки /* Парсинг https://stackoverflow.com/questions/35227449/convert-ip-or-mac-address-from-string-to-byte-array-arduino-or-c */ void parseBytes(const char* str, char sep, byte* bytes, int maxBytes, int base) { for (int i = 0; i < maxBytes; i++) { bytes[i] = strtoul(str, NULL, base); str = strchr(str, sep); if (str == NULL || *str == '\0') break; str++; } } В нашем случае выглядеть это будет следующим образом
      byte ip[4]; parseBytes(inputCommands.substring(4).c_str(), '.', ip, 4, 10); А дале все становится еще проще, попросту проверить попадает ли наш ip адрес, в список правильных адресов. И самой простой проверкой послужит проверка первого байта адреса на несоответствие не угодным нам сетям (0, 127, 255)
      if (ip[0] != 127 and ip[0] != 255 and ip[0] != 0) { // Производим необходимые нам действия с ip адресом, например, запись в конфиг config.ip = {ip[0], ip[1], ip[2], ip[3]}; } Вы в праве реализовать собственные проверки, какие только душе угодны.
      Также хотелось бы отметить, что обрабатывать некоторые параметры проще и быстрее через их короткие записи. К таким можно отнести маску подсети устройства. Например, привычный дня нас адрес 192.168.0.1 с маской подсети 255.255.255.0 можно записать в виде 192.168.0.1/24, где цифра 24 указывает нашу подсеть в краткой форме. А, следовательно, мы можем записать несколько кратких форм масок подсети в следующем виде:
      subnet 255.255.255.0 или subnet 24 subnet 255.255.0.0 или subnet 16 subnet 255.0.0.0 или subnet 8 Это основные маски, и я не описывал все существующие т.к в этом нет нужды, но если Вам интересно, то почитать про них можно в wikipedia.
      if (inputCommands.startsWith(F("subnet"))) {     String input = inputCommands.substring(8);     if (input == F("24"))      config.subnet = {255, 255, 255,   0};     else if (input == F("16")) config.subnet = {255, 255,   0,   0};     else if (input == F("8"))  config.subnet = {255,   0,   0,   0};     else { // Все остальные маски попадают в этот блок         byte subnet[4];         parseBytes(input.c_str(), '.', subnet, 4, 10); config.subnet = {subnet[0], subnet[1], subnet[2], subnet[3]};     } } MAC адрес хранится у нас в виде массива байт. Его перезапись другим массивом производится с помощью функции memcpy
      if (inputCommands.startsWith(F("mac"))) { byte mac[6]; parseBytes(inputCommands.substring(4).c_str(), ':', mac, 6, 16); memcpy(config.mac, mac, 6); } Изменение адреса MQTT сервера
      if (inputCommands.startsWith(F("server"))) { String server = inputCommands.substring(8); server.trim(); if (server.length() < 40) server.toCharArray(config.server, 40); } В принципе теперь понятно, как производить получение, разбор и сохранение конфигурации в EEPROM микроконтроллера.
      Как это выглядит на практике
      Заливаем программу в микроконтроллер и подключаемся к Arduino по usb или через переходник. Открываем терминал и нас приветствуют краткой справкой с описанием доступных команд.
      - --------------------------------------------------------------------------------------- # Sensor with data sending to mqtt server (c) it4it.club # Use the "config" command to view the current configuration # To change the configuration, specify the parameter name and its new value with a space, # for example "ip 192.168.0.200", "subnet 255.255.255.0" or "mac AA:BB:CC:DD:EE:FF" # You can also specify a subnet using the mask 24, 16 or 8 # Additional commands: # sensors - view current data from sensors # config - view current configuration # save - saves the current configuration # reset - resets all settings # restart - restarts the device # eeprom clear - removes all contents of eeprom # help - view this help - --------------------------------------------------------------------------------------- Т.к. в EEPROM микроконтроллера не была обнаружена конфигурация (волшебный hex байт нам подсказал), то были задействованы стандартные настройки. Просмотреть текущую конфигурацию можно командой config
      config # ip: 192.168.0.200 # subnet: 255.255.255.0 # gateway: 192.168.0.1 # dns: 192.168.0.1 # mac: 00:AA:BB:CC:DE:02 # server: mqtt.it4it.club # topic: arduino/serial/config чтобы изменить значение любого из этих параметров необходимо передать имя параметра и его новое значение, разделенные между собой пробелом.
      ip 10.10.10.99 # ok gateway 10.10.10.1 # ok dns 10.10.10.1 # ok После окончания конфигурации необходимо сохранить настройки и если были затронуты критические параметры, например, сеть, то перезапустить Arduino соответствующей командой.
      save # ok restart # ok # restarting device... Если параметр был успешно принят, то контроллер ответит нам "ok", а в противном случае ругнется.
      ip 127.0.0.1 # bad ip Также мы получим негативный ответ если команда не была распознана.
      qwerqwer1243 # bad command С остальными командами Вы разберетесь самостоятельно.


      Исходник: MQTT_CLIENT_328_SERIAL_CONFIG.zip
      PS: в общем то это статья родилась только для того, чтобы в соседнем форуме с системой мониторинга Zabbix появилась ссылка на устройство, но я надеюсь, что она также станет полезна любителям домашней автоматизации и не только.
  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу.

×
×
  • Создать...