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

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

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

нет ну то что не будет работать с базой данных без езернета - понятно.

но может можно как-то добавить чтобы в случае сбоя роутера можно было открыть кнопкой и хотябы парочкой карт?

а  то выходит систама "в заперти"

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


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

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

Что по поводу кнопки. Я буду рассуждать в контексте оригинальной программы.

Реализация механизма открытия с кнопки очень проста и работает независимо от сети.

// Открытие двери с кнопки
key_open.update();
if(!key_open.read() and openTimer == 0 and !mode) {
  if(modeLock or (!modeLock and digitalRead(PIN_RELAY))) {
    openTimer = millis()/1000;
    digitalWrite(PIN_RELAY, LOW);
    Serial.println(F("The door opened from the inside\n"));
    squeaker(5, 3200, 100, 300);
  }
  delay(2000);
}

На работоспособность кнопки влияет смена режима работы замка - режим программирования. В этом состоянии замок открыт и ожидает новые ключи для записи в eeprom.

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

  1. В каком режиме работает закрытие замка (авто/ручной)?
  2. Используете ли Вы сторожевой таймер?
  3. Внесите следующие изменение в работу кнопки
    Скрытый текст
    
    // Открытие двери с кнопки
    key_open.update();
    if(!key_open.read() and openTimer == 0 and !mode) {
      Serial.println(F("Key down")); // Ожидаем увидеть этот вывод в Serial
      if(modeLock or (!modeLock and digitalRead(PIN_RELAY))) {
        openTimer = millis()/1000;
        digitalWrite(PIN_RELAY, LOW);
        Serial.println(F("The door opened from the inside\n")); // И этот тоже
        squeaker(5, 3200, 100, 300);
      }
      delay(2000);
    }
  4. Воссоздайте ситуацию и опишите полный порядок Ваших действий.
  5. Что прилетает в Serial монитор?

 

 

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


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

 

Что по поводу кнопки. Я буду рассуждать в контексте оригинальной программы.

Реализация механизма открытия с кнопки очень проста и работает независимо от сети.


// Открытие двери с кнопки
key_open.update();
if(!key_open.read() and openTimer == 0 and !mode) {
  if(modeLock or (!modeLock and digitalRead(PIN_RELAY))) {
    openTimer = millis()/1000;
    digitalWrite(PIN_RELAY, LOW);
    Serial.println(F("The door opened from the inside\n"));
    squeaker(5, 3200, 100, 300);
  }
  delay(2000);
}

На работоспособность кнопки влияет смена режима работы замка - режим программирования. В этом состоянии замок открыт и ожидает новые ключи для записи в eeprom.

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

  1. В каком режиме работает закрытие замка (авто/ручной)?
  2. Используете ли Вы сторожевой таймер?
  3. Внесите следующие изменение в работу кнопки
      Показать содержимое
    
    
    // Открытие двери с кнопки
    key_open.update();
    if(!key_open.read() and openTimer == 0 and !mode) {
      Serial.println(F("Key down")); // Ожидаем увидеть этот вывод в Serial
      if(modeLock or (!modeLock and digitalRead(PIN_RELAY))) {
        openTimer = millis()/1000;
        digitalWrite(PIN_RELAY, LOW);
        Serial.println(F("The door opened from the inside\n")); // И этот тоже
        squeaker(5, 3200, 100, 300);
      }
      delay(2000);
    }
  4. Воссоздайте ситуацию и опишите полный порядок Ваших действий.
  5. Что прилетает в Serial монитор?

 

 

1) - автоматическое закрытие серез время

2) да. скетч в этой части не изменылся. стоит ( wdt_disable();
  delay(8000);
  wdt_enable(WDTO_8S);)

3) внес но попрежнему без ethernet не открывает.

4) вариант1 - подключаю езернет, подключаю юсб. - все работает отлично. 

вариант2 - подключаю езернет, подключаю юсб (питание) - все отлично, вытаскиваю езернет - кнопка работает на карточки не реагирует.

вариант3 - НЕ подключаю езернет, подключаю юсб (питание) - карточка не работает, КНОПКА также не работает.

5) ничего. молчит. просто пустой

в скетч была дописана функция открытия без ключа. но также не хочет работать( может что-т не так?)

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

 


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

// Необходимые пины
#define PIN_SS         9  // RFID
#define PIN_RST        8  // RFID

#define PIN_RELAY      7  // RELAY
#define PIN_MODE       6  // MODE
#define PIN_TONE       3  // TONE
#define PIN_OPEN       2  // OPEN

MFRC522 mfrc522(PIN_SS, PIN_RST);

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress server(11, 226, 221, 35);  // IP адрес MySQL сервера
char user[] = "p111";               // MySQL username
char password[] = "2222";        // MySQL password

const char QUERY_S[] = "SELECT type FROM prescent_crm1.rfid WHERE uid = %s;";
const char QUERY_I[] = "INSERT INTO prescent_crm1.rfid (uid) VALUES ('%s');";
char query[128];

EthernetClient client;
MySQL_Connection conn((Client *)&client);
MySQL_Cursor cur = MySQL_Cursor(&conn);

//
String key, lastKey;
String typeKey;
char toQuery[12];
unsigned long lastKeyTimer  = 0;

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

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

// Защита кнопок от дребезга
Bounce key_open  = Bounce();
/*
  Функция звукового оповещения.
  Принимает параметры: количество звуковых сигналов, частота в герцах, продолжительность звука, пауза в миллисекундах (не обязательно)
*/
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 setup() {
  // Настраиваем сторожевой таймер
  wdt_disable();
  delay(8000);
  wdt_enable(WDTO_8S);

  // Инициализация используемых пинов
  // Реле
  pinMode(PIN_RELAY, OUTPUT);
  digitalWrite(PIN_RELAY, HIGH);
   
  // Кнопка открытия двери
  pinMode(PIN_OPEN,INPUT_PULLUP);
  key_open.attach(PIN_OPEN);
  key_open.interval(5);   

  // Перемычка выбора режима работы (0 - автоматическое закрытие двери, 1 - закрытие двери по ключу)
  pinMode(PIN_MODE, INPUT);
  digitalWrite(PIN_MODE, HIGH);

  // Инициализация консоли
  Serial.begin(115200);
  while (!Serial);

  // Инициализация ридера
  SPI.begin();
  mfrc522.PCD_Init();

  Ethernet.begin(mac);
  Serial.print(F("IP: "));
  Serial.println(Ethernet.localIP());
  Serial.println();
  
  // Приглашаем в гости
  Serial.println(F("iT4iT CLUB (C) 2015\nhttps://it4it.club\n"));      
}

void loop() {
  // Сбрасываем сторожевой таймер микроконтроллера
  wdt_reset();

  if(openTimer > millis()+10000) openTimer = 0;
  if(lastKeyTimer > millis()+10000) lastKeyTimer = 0;

  // Изменение режима работы закрытия
  modeLock = !digitalRead(PIN_MODE);

  // Открытие двери с кнопки
  key_open.update();
  if(!key_open.read() and openTimer == 0 and !mode) {
    if(modeLock or (!modeLock and digitalRead(PIN_RELAY))) {
      openTimer = millis()/1000;
      digitalWrite(PIN_RELAY, LOW);
      Serial.println(F("The door opened from the inside\n"));
      squeaker(5, 3200, 100, 300);
    }
    delay(2000);
  }
  
  // Автоматическое закрытие двери
  if(openTimer != 0) {
    if(millis()/1000 - openTimer > 5) {
      openTimer = 0;
      if(modeLock) {
        digitalWrite(PIN_RELAY, HIGH);
        Serial.println(F("* closed lock"));
      }
      else {
        if(!digitalRead(PIN_RELAY)) digitalWrite(PIN_RELAY, HIGH);
      }
    }
  }
  
  // Если ключ отсутствует или не читается, не выполняем дальнейший код
  if(!mfrc522.PICC_IsNewCardPresent()) {
    // Очистка таймера входа в режим программирования, в случае если ридер свободен
    if(modeTimer != 0) {
      if(++modeClean > 5) modeTimer = modeClean = 0;
    }
    // Таймер для контроля времени хранения последнего ключа в памяти
    if(lastKeyTimer != 0) {
      if((millis()-lastKeyTimer) > 100) {
        lastKey = "";
        lastKeyTimer = 0;
      }
    }
    return;
  }
  if(!mfrc522.PICC_ReadCardSerial()) return;
  // Останавливаем режим очистки
  modeClean = 0;

  // Читаем ключ
  for(byte i=0; i<4; i++) key += mfrc522.uid.uidByte[i];
  // Один ключ = Один запрос к MySQL серверу. Не флудим.
  if(key != lastKey) {
    
    if(!conn.connected())
    {

      //without Internet
      if(key=="7719892131" || key=="2145683647" || key=="8624152187")
      {
        if(!mode) {
          Serial.println(F("access allow"));
          // Доступ разрешен
          if(modeLock) {
            openTimer = millis()/1000;
            digitalWrite(PIN_RELAY, LOW); 
            return;
          }
          else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
          squeaker(2, 2200, 200, 200);
          return;
        }
      }
      
      if(!conn.connect(server, 3306, user, password)) {
        squeaker(5, 1000, 200, 200);
        return;
      }
    }

    key.toCharArray(toQuery, key.length()+1);
    Serial.println(F("key code -"));
    Serial.println(toQuery);
    sprintf(query, QUERY_S, toQuery);
    cur.execute(query);

    column_names *cols = cur.get_columns();
    row_values *row = NULL;
    Serial.println("KEY: " + key);
    if((row = cur.get_next_row()) != NULL) typeKey = String(row->values[0]); else typeKey = "0";
    if(typeKey == F("1") or typeKey == F("2")) {
      if(!mode) {
        Serial.println(F("access allow"));
        // Доступ разрешен
        if(modeLock) {
          openTimer = millis()/1000;
          digitalWrite(PIN_RELAY, LOW); 
        }
        else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
        squeaker(2, 2200, 200, 200);
      }
      else {
        if(typeKey != F("1")) {
          Serial.println(F("error: key elrady exists in eeprom"));
          squeaker(2, 500, 300);
        }
      }
    }
    else {
      if(!mode) {
        Serial.println(F("access dany"));
        squeaker(1, 500, 1000);
      }
      else {
        Serial.println(F("add key in MySQL"));
        sprintf(query, QUERY_I, toQuery);
        cur.execute(query);
        squeaker(2, 2200, 200, 200);
      }
    }

    lastKey = key;
    cur.close();
    conn.close();
  }
  else {
    if(typeKey == F("1")) {
      if(modeTimer == 0) modeTimer = millis()/1000;
      else {
        if(millis()/1000 - modeTimer > modeProgTime and modeTimer != 0) {
          modeTimer = openTimer = 0;
          if((mode = !mode) == true) {
            // Вход в режим программирования
            digitalWrite(PIN_RELAY, LOW);
            Serial.println(F("MASTER PROGRAMMING MODE ON"));
            squeaker(4, 1200, 200, 200);
          }
          else {
            // Выход из режима программирования
            digitalWrite(PIN_RELAY, HIGH);
            Serial.println(F("MASTER PROGRAMMING MODE OFF"));
            squeaker(4, 2200, 200, 200);
          }
          delay(2000);
        }
      }
    }
  }
  
  lastKeyTimer = millis();
  key = "";
}

в часности:

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

   //without Internet
      if(key=="7719892131" || key=="2145683647" || key=="8624152187")
      {
        if(!mode) {
          Serial.println(F("access allow"));
          // Доступ разрешен
          if(modeLock) {
            openTimer = millis()/1000;
            digitalWrite(PIN_RELAY, LOW); 
            return;
          }
          else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
          squeaker(2, 2200, 200, 200);
          return;
        }
      }
      
      if(!conn.connect(server, 3306, user, password)) {
        squeaker(5, 1000, 200, 200);
        return;
      }
    }

 

 

 

 

но также не помогло(

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

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


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

@Slava хорошо, давайте отталкиваться от этой информации. Но хотелось бы уточнить, скорость Serial монитор совпадала со скоростью выставленной в контроллере? По умолчанию 115200.

Что бросилось в глаза:

4 часа назад, Slava сказал:

вариант2 - подключаю езернет, подключаю юсб (питание) - все отлично, вытаскиваю езернет - кнопка работает на карточки не реагирует.

вариант3 - НЕ подключаю езернет, подключаю юсб (питание) - карточка не работает, КНОПКА также не работает.

а также то, что

4 часа назад, Slava сказал:

5) ничего. молчит. просто пустой

Т.к Вы не увидели никакой информации, в том числе и приветствие, я делаю вывод, что функция Setup не была завершена, а значит, до функции loop дело не дошло. Это объясняет, почему не отрабатывает кнопка, как и все остальные функции замка. Также, учитывая, что во втором варианте, при старте с сетью и последующем отключении от неё, кнопка продолжала работать, можно сделать вывод, что программа вышла из функции Setup и выполняет loop.

Могу предположить, что при старте контроллер пытается инициализировать сетевое соединение и т.к ожидаются настройки от DHCP сервера, которых мы не получим из-за отсутствия связи, то на ожидание уходит какое-то время. И вероятнее всего, оно превышает установленный придел времени для сторожевого таймера, что приводит к перезапуску контроллера. И данный цикл повторяется до появления сети. Думаю, это можно проверить, запустив контроллер без сети, и открыв Serial монитор, подключить сеть обратно.

Предлагаю попробовать уйти от DHCP сервера и прописать всю необходимую информацию самостоятельно. Но перед этим добавим тестовый вывод после инициализации Serial.

Найдите

Serial.begin(115200);
while (!Serial);

Добавьте тестовый вывод

Serial.begin(115200);
while (!Serial);
Serial.println(F("Start"));

Далее пропишем все данные для сети самостоятельно. Найдите в функции Setup строку

Ethernet.begin(mac);

И замените её на следующий блок

IPAddress ip(192, 168, 0, 100);
IPAddress subnet(255, 255, 255, 0);  
IPAddress gateway(192, 168, 0, 1);
IPAddress dns(192, 168, 0, 1);

Ethernet.begin(mac, ip, dns, gateway, subnet);

Укажите настройки, соответствующие Вашей сети.

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


Ссылка на сообщение
Поделиться на других сайтах
В 14.11.2017 в 15:09, Kitsum сказал:

 

Спасибо. вродебы пока не нашел больше багов.

И на основе домофонна появилась идея улучшить кофе апарат в офисе)

пополняется баланс на карточке. и когда подносишь карточку - проверяет есть ли средства - если есть - включает релле и минусует баланс.

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

помогите плз. может опытным глазом тыкнете на ошибку.

 

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


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

// Необходимые пины
#define PIN_SS         9  // RFID
#define PIN_RST        8  // RFID

#define PIN_RELAY      7  // RELAY
#define PIN_MODE       6  // MODE
#define PIN_TONE       3  // TONE
#define PIN_OPEN       2  // OPEN

MFRC522 mfrc522(PIN_SS, PIN_RST);

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress server(91, 206, 201, 35);  // IP адрес MySQL сервера
char user[] = "prescent_crm1";               // MySQL username
char password[] = "uae6tkrc";        // MySQL password

const char QUERY_S[] ="Select Sum(Sum*MoneyWay) From prescent_crm1.CoffeMoneys where User_id=(SELECT User_id FROM prescent_crm1.RFIDKeys WHERE uid = '%s' and IsActive=1 Limit 1);";
const char QUERY_I[] = "INSERT INTO prescent_crm1.CoffeMoneys (Sum, MoneyWay, User_id) VALUES (3,-1,(SELECT User_id FROM prescent_crm1.RFIDKeys WHERE uid = '%s' and IsActive=1 Limit 1));";
const char QUERY_IT[] = "INSERT INTO prescent_crm1.CoffeMoneys (Sum, MoneyWay, User_id) VALUES (3,-1,1);";
char query[128];

EthernetClient client;
MySQL_Connection conn((Client *)&client);
MySQL_Cursor cur = MySQL_Cursor(&conn);

//
String key, lastKey;
String typeKey;
char toQuery[12];
unsigned long lastKeyTimer  = 0;

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

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

// Защита кнопок от дребезга
Bounce key_open  = Bounce();
/*
  Функция звукового оповещения.
  Принимает параметры: количество звуковых сигналов, частота в герцах, продолжительность звука, пауза в миллисекундах (не обязательно)
*/
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 setup() {
  // Настраиваем сторожевой таймер
  wdt_disable();
  delay(8000);
  wdt_enable(WDTO_8S);

  // Инициализация используемых пинов
  // Реле
  pinMode(PIN_RELAY, OUTPUT);
  digitalWrite(PIN_RELAY, HIGH);
   
  // Кнопка открытия двери
  pinMode(PIN_OPEN,INPUT_PULLUP);
  key_open.attach(PIN_OPEN);
  key_open.interval(5);   

  // Перемычка выбора режима работы (0 - автоматическое закрытие двери, 1 - закрытие двери по ключу)
  pinMode(PIN_MODE, INPUT);
  digitalWrite(PIN_MODE, HIGH);

  // Инициализация консоли
  Serial.begin(115200);
  while (!Serial);
  Serial.println(F("Start"));

  // Инициализация ридера
  SPI.begin();
  mfrc522.PCD_Init();

  IPAddress ip(192, 168, 0, 222);
  IPAddress subnet(255, 255, 255, 0);  
  IPAddress gateway(192, 168, 0, 1);
  IPAddress dns(192, 168, 0, 2);

Ethernet.begin(mac, ip, dns, gateway, subnet);
  Serial.print(F("IP: "));
  Serial.println(Ethernet.localIP());
  Serial.println();
  
  // Приглашаем в гости
  Serial.println(F("iT4iT CLUB (C) 2015\nhttps://it4it.club\n"));      
}

void loop() {
  // Сбрасываем сторожевой таймер микроконтроллера
  wdt_reset();

  if(openTimer > millis()+10000) openTimer = 0;
  if(lastKeyTimer > millis()+10000) lastKeyTimer = 0;

  // Изменение режима работы закрытия
  //modeLock = !digitalRead(PIN_MODE);

  // Открытие двери с кнопки
  key_open.update();

  // Если ключ отсутствует или не читается, не выполняем дальнейший код
  if(!mfrc522.PICC_IsNewCardPresent()) {
    // Очистка таймера входа в режим программирования, в случае если ридер свободен
    if(modeTimer != 0) {
      if(++modeClean > 5) modeTimer = modeClean = 0;
    }
    // Таймер для контроля времени хранения последнего ключа в памяти
    if(lastKeyTimer != 0) {
      if((millis()-lastKeyTimer) > 100) {
        lastKey = "";
        lastKeyTimer = 0;
      }
    }
    return;
  }
  if(!mfrc522.PICC_ReadCardSerial()) return;
  // Останавливаем режим очистки
  modeClean = 0;

  // Читаем ключ
  for(byte i=0; i<4; i++) key += mfrc522.uid.uidByte[i];
  // Один ключ = Один запрос к MySQL серверу. Не флудим.
  if(key != lastKey) 
  {
    
  
      
      if(!conn.connect(server, 3306, user, password)) {
        squeaker(5, 1000, 200, 200);
       return;
      }

    key.toCharArray(toQuery, key.length()+1);
    Serial.println(F("key code -"));
    Serial.println(toQuery);
    sprintf(query, QUERY_S, toQuery);
    cur.execute(query);

    column_names *cols = cur.get_columns();

    for (int f = 0; f < cols->num_fields; f++) {
    Serial.print(cols->fields[f]->name);
    if (f < cols->num_fields-1) {
      Serial.print(',');
    }
  }
  
    row_values *row = NULL;
    Serial.println("KEY: " + key);
    row = cur.get_next_row();
    if(row != NULL)
    {
      if(row->values[0]!=NULL)
      {
      typeKey = String(row->values[0]);
      }
      else
      {
        typeKey = String("0");
      }
    }
    else
    {
      typeKey = "0";
    //}
    }
    Serial.println("FROMDB: " + typeKey);
    if((typeKey.toFloat()) > 3) 
    {
      //if(!mode) 
      {
        Serial.println(F("access allow"));
        // Доступ разрешен
        if(modeLock) 
        {
          openTimer = millis()/1000;
          digitalWrite(PIN_RELAY, LOW); 

          Serial.println(F("add key in MySQL"));
       // sprintf(query, QUERY_IT, toQuery);
        //cur.execute(QUERY_IT);

cur.close();
    conn.close();
    
if(!conn.connect(server, 3306, user, password)) {
        squeaker(5, 1000, 200, 200);
       return;
      }
      
        MySQL_Cursor cur_mem = MySQL_Cursor(&conn);

  cur_mem.execute(QUERY_IT);
  cur_mem.close();
  
        }
        else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
        squeaker(2, 2200, 200, 200);


        
      }
    }
    else {
      //if(!mode) 
      {
        Serial.println(F("access dany"));
        squeaker(1, 500, 1000);
      }
    }

    lastKey = key;
    cur.close();
    conn.close();
  }
    lastKey = key;
  lastKeyTimer = millis();
  key = "";
}

 

 

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


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

@Slava Мое личное мнение, что Вам стоит пересмотреть запросы к базе и уже от этого отталкиваться при редактировании кода микроконтроллера. Уверен, что контроллеру достаточно понимать имеется ли в базе учетная запись, подходящая под критерии необходимые для доступа к устройству. А всю математику реализовать на стороне MySQL сервера. Таким образом контроллер просто передает идентификатор ключа, СУБД производит поиск учетной записи соответствующей этому ключу с дополнительным условием - остаток на счету должен превышать установленный минимум. Если запись найдена, то силами самой базы данных, производится обновление баланса пользователя, а контроллер лишь смотрит на количество соответствий. Соответствие найдено, включаем нагрузку, не найдено, значит не включаем. Вы можете на самом MySQL сервере реализовать функцию,  которой в качестве параметра, будет передаваться идентификатор ключа. Вся логика будет заложена именно в ней, а от возвращаемого ею результата будет зависеть управление нагрузкой.

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


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

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

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

Из схемы что изменено:

- нет спикера, не поставил; (временно)

-  вместо кнопки  открытия подтянул к 2 пину через 10кОм +5в; (временно)

-  вместо переключателя режима, подтянул к 6 пину землю. (временно)

В базе данных при создании таблицы поменял тип переменной uid с bigint на var  (т.к. ключи все подобные 16B7FB08, а по другому не могу вставить в базу)

Проблема следующая на данный момент, в мониторе появляется: 

Start
IP: 192.168.1.113

iT4iT CLUB (C) 2015
https://it4it.club

 

 

На всякий случай запустил пинг ардуины на компе, подношу ключ (любой), гаснет диод (который на 13ой ноге), потом очень тускло светится секунд 8,  потом вообще тухнет опять секунд 8, и загорается вновь.

в мониторе появляется опять тоже самое. Пинг не пропадает.

ЧТО ЭТО МОЖЕТ БЫТЬ? Всем спасибо за помощь. С Праздником!!!

 

 

Код который заливаю в ардуину
 

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

 


// Необходимые библиотеки 16 B7 FB 08
#include <avr/wdt.h>          
#include <SPI.h>
#include <MFRC522.h>
#include <Bounce2.h>
#include <Ethernet.h>
#include <MySQL_Connection.h>
#include <MySQL_Cursor.h>

// Необходимые пины
#define PIN_SS         9  // RFID
#define PIN_RST        8  // RFID

#define PIN_RELAY      7  // RELAY7
#define PIN_MODE       6  // MODE
#define PIN_TONE       3  // TONE
#define PIN_OPEN       2  // OPEN

MFRC522 mfrc522(PIN_SS, PIN_RST);

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress server(192, 168, 1, 111);  // IP адрес MySQL сервера
char user[] = "root";               // MySQL username
char password[] = "";        // MySQL password

const char QUERY_S[] = "SELECT type FROM test.rc522 WHERE uid = %s;";
const char QUERY_I[] = "INSERT INTO test.rc522 (uid) VALUES ('%s');";
char query[128];

EthernetClient client;
MySQL_Connection conn((Client *)&client);
MySQL_Cursor cur = MySQL_Cursor(&conn);

//
String key, lastKey;
String typeKey;
char toQuery[12];
unsigned long lastKeyTimer  = 0;

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

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

// Защита кнопок от дребезга
Bounce key_open  = Bounce();
/*
  Функция звукового оповещения.
  Принимает параметры: количество звуковых сигналов, частота в герцах, продолжительность звука, пауза в миллисекундах (не обязательно)
*/
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 setup() {
  // Настраиваем сторожевой таймер
  wdt_disable();
  delay(8000);
  wdt_enable(WDTO_8S);

  // Инициализация используемых пинов
  // Реле
  pinMode(PIN_RELAY, OUTPUT);
  digitalWrite(PIN_RELAY, HIGH);
   
  // Кнопка открытия двери
  pinMode(PIN_OPEN,INPUT_PULLUP);
  key_open.attach(PIN_OPEN);
  key_open.interval(5);   

  // Перемычка выбора режима работы (0 - автоматическое закрытие двери, 1 - закрытие двери по ключу)
  pinMode(PIN_MODE, INPUT);
  digitalWrite(PIN_MODE, HIGH);

  // Инициализация консоли
  Serial.begin(115200);
  while (!Serial);
  Serial.println(F("Start"));

  // Инициализация ридера
  SPI.begin();
  mfrc522.PCD_Init();

 // Ethernet.begin(mac);
  IPAddress ip(192, 168, 1, 113);
  IPAddress subnet(255, 255, 255, 0);  
  IPAddress gateway(192, 168, 1, 1);
  IPAddress dns(192, 168, 1, 1);

  Ethernet.begin(mac, ip, dns, gateway, subnet);
 
  Serial.print(F("IP: "));
  Serial.println(Ethernet.localIP());
  Serial.println();
  
  // Приглашаем в гости
  Serial.println(F("iT4iT CLUB (C) 2015\nhttps://it4it.club\n"));      
}

void loop() {
  // Сбрасываем сторожевой таймер микроконтроллера
  wdt_reset();

  if(openTimer > millis()+10000) openTimer = 0;
  if(lastKeyTimer > millis()+10000) lastKeyTimer = 0;

  // Изменение режима работы закрытия
  modeLock = !digitalRead(PIN_MODE);

  // Открытие двери с кнопки
  key_open.update();
  if(!key_open.read() and openTimer == 0 and !mode) {
    if(modeLock or (!modeLock and digitalRead(PIN_RELAY))) {
      openTimer = millis()/1000;
      digitalWrite(PIN_RELAY, LOW);
      Serial.println(F("The door opened from the inside\n"));
      squeaker(5, 3200, 100, 300);
    }
    delay(2000);
  }
  
  // Автоматическое закрытие двери
  if(openTimer != 0) {
    if(millis()/1000 - openTimer > 5) {
      openTimer = 0;
      if(modeLock) {
        digitalWrite(PIN_RELAY, HIGH);
        Serial.println(F("* closed lock"));
      }
      else {
        if(!digitalRead(PIN_RELAY)) digitalWrite(PIN_RELAY, HIGH);
      }
    }
  }
  
  // Если ключ отсутствует или не читается, не выполняем дальнейший код
  if(!mfrc522.PICC_IsNewCardPresent()) {
    // Очистка таймера входа в режим программирования, в случае если ридер свободен
    if(modeTimer != 0) {
      if(++modeClean > 5) modeTimer = modeClean = 0;
    }
    // Таймер для контроля времени хранения последнего ключа в памяти
    if(lastKeyTimer != 0) {
      if((millis()-lastKeyTimer) > 100) {
        lastKey = "";
        lastKeyTimer = 0;
      }
    }
    return;
  }
  if(!mfrc522.PICC_ReadCardSerial()) return;
  // Останавливаем режим очистки
  modeClean = 0;

  // Читаем ключ
  for(byte i=0; i<4; i++) key += mfrc522.uid.uidByte[i];
  // Один ключ = Один запрос к MySQL серверу. Не флудим.
  if(key != lastKey) {
    if(!conn.connected()) {
      if(!conn.connect(server, 3306, user, password)) {
        squeaker(5, 1000, 200, 200);
        return;
      }
    }
    
    key.toCharArray(toQuery, key.length()+1);
    sprintf(query, QUERY_S, toQuery);
    cur.execute(query);

    column_names *cols = cur.get_columns();
    row_values *row = NULL;
    Serial.println("KEY: " + key);
    if((row = cur.get_next_row()) != NULL) typeKey = String(row->values[0]); else typeKey = "0";
    if(typeKey == F("1") or typeKey == F("2")) {
      if(!mode) {
        Serial.println(F("access allow"));
        // Доступ разрешен
        if(modeLock) {
          openTimer = millis()/1000;
          digitalWrite(PIN_RELAY, LOW); 
        }
        else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
        squeaker(2, 2200, 200, 200);
      }
      else {
        if(typeKey != F("1")) {
          Serial.println(F("error: key elrady exists in eeprom"));
          squeaker(2, 500, 300);
        }
      }
    }
    else {
      if(!mode) {
        Serial.println(F("access dany"));
        squeaker(1, 500, 1000);
      }
      else {
        Serial.println(F("add key in MySQL"));
        sprintf(query, QUERY_I, toQuery);
        cur.execute(query);
        squeaker(2, 2200, 200, 200);
      }
    }

    lastKey = key;
    cur.close();
    conn.close();
  }
  else {
    if(typeKey == F("1")) {
      if(modeTimer == 0) modeTimer = millis()/1000;
      else {
        if(millis()/1000 - modeTimer > modeProgTime and modeTimer != 0) {
          modeTimer = openTimer = 0;
          if((mode = !mode) == true) {
            // Вход в режим программирования
            digitalWrite(PIN_RELAY, LOW);
            Serial.println(F("MASTER PROGRAMMING MODE ON"));
            squeaker(4, 1200, 200, 200);
          }
          else {
            // Выход из режима программирования
            digitalWrite(PIN_RELAY, HIGH);
            Serial.println(F("MASTER PROGRAMMING MODE OFF"));
            squeaker(4, 2200, 200, 200);
          }
          delay(2000);
        }
      }
    }
  }
  
  lastKeyTimer = millis();
  key = "";
}

 

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

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


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

Я описывал, но повторюсь :), появляется тоже самое что и при старте контроллера.

Проблему нашел( порт 3306 на сервере в брандмауэре был закрыт). Незнаю почему, но для апатча и скюэля были все порты открыты по умолчанию, но как только прописал открытие порта 3306 вручную, все заработало. 

Проблема теперь в другом, отклик от момента подноса ключа к считывателю и открытием около 8ми секунд, это многовато для меня. Пробовал на другом компе с процем i7 и т.д. тоже самое. С чем может это быть связано? 

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


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

Доброе время суток. Подскажите пожалуйста, как добавить к вашему проекту (который описан в начале поста) LCD дисплей, что бы информация выводилась на него. У меня LCD 1602A подключение по I2C на пины А5 и А4. Заранее вам спасибо!

 

  • Like 1

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


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

@valdemar у меня отклик был порядка 3-4 секунд, но даже этого будет не достаточно если Вы хотите какого либо серьезного применения. Лучшим выбором будет отказаться от обработки запросов к базе данных на уровне микроконтроллера. Использовать http, а лучше https соединение для передачи ключа внешнему скрипту на сервере, где произойдет выборка и обработка данных из базы, а микроконтроллер в ответ получит статус запрашиваемого ключа. Или использовать socket-ы с предварительным шифрованием передаваемых данных, но проще и быстрее использовать https. Уверен, что таким образом можно добиться времени, с момента поднесения ключа до открытия замка, не более 2 секунд в локальной сети.

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

 

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


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

@Kitsum , а если попробовать хранить данные ключей на микросд карте, которая на борту изернет шилда? а Изернет шилд использовать для добавления или удаления ключей?

 

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


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

@Kitsum у меня в Садоводческом Обществе 1046 участков, у каждого участка минимум 2 ключа, причем дополнительно проводится проверка на факт задолженности при поднесении ключа. Епром не потянет такое количество ключей это факт, поэтому есть большое желание решить поставленную задачу..

 

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


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

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

  1. Если все 1046 участков будут оборудованы замками, то зачем в каждый из них прописывать все доступные ключи? Полагаю, что вход на участок доступен только собственникам участка. Как Вы уже упомянули, это минимум 2 ключа. В eeprom микроконтроллера atmega328 равен 1024 байтам, по 4 байта на ключ. Итого 255 ключей + 4 байта для дополнительной информации. Если ключи соседей не должны открывать замки друг у друга, то 255 ключей более чем достаточно на одного владельца участка.
  2. Если все замки будут подключены в общую сеть, то как Вы планируете её организовывать или она уже есть? Какую площадь занимает сеть?
  3. Если замки и так будут в сети, то опять же на мой взгляд, нет смысла вязаться с EEPROM, если только для хранения спец ключей - мастер, аварийный или для обслуживающего персонала. Вы также планируете вести учет, но с прописанными ключами в памяти замка, что помешает собственнику участка откинуть сеть и использовать уже прописанные ключи?
  4. Думаю, что при времени отклика порядка 2 секунд, можно хранить ключи на центральном сервере.

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


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

@Kitsum Не у каждого на калитке будет замок, у нас общество огорожено, есть 2 официальных входа: 1ый просто калитка, 2ой калитка и автоматический шлагбаум. Сами замки будут установлены непосредственно на КПП. Задача 1 избежать проход посторонних, задача 2 создать неудобства неплательщикам взносов. Шлагбаум уже управляется телефонами, но в дальнейшем хочу тоже к нему прикрутить нрф, т.к. связь в нашем регионе бывает лагает.
Сеть покрыта плотно т.к. год назад растянул оптику и на ключевых местах висят айпикамеры, потиху докупаемся.

НУ КАК-ТО ТАК :)

 

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


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

Уважаемые ! Хочу предложить вот такие качественные замки по демократичной цене ! Всего-то хочу 2500 . На форуме я проверенный человек кидалово не предвидится. https://www.perco.ru/products/vreznoy-elektromekhanicheskiy-zamok-lc85.4.php

  • Like 1

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


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

Ребят, ну мысли есть какие - нибудь?
Может кто подскажет как осуществить поиск ключа  на совпадение записанного в txt файле на флеш карте?

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


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

@valdemar доброе время суток. Осмелюсь предложить Вам провести эксперимент:

  1. Поднимите сервер Apache на любой машине в Вашей сети
  2. Создайте на сервере страницу с одним символом 0 или 1, без html разметки и т.п, просто один символ
  3. В любой доступный Вам контроллер залейте стандартный скетч работы с Ethernet идущий в комплекте с Arduino IDE - Web client. Только измените адрес запрашиваемой страницы на адрес Вашего сервера с Apache.
  4. Посмотрите с какой скоростью прилетает ответ т.к эта скорость, по сути, и будет временем отклика при работе с MySQL

Уверен, что отклик будет удовлетворительный. На мой взгляд, работа с SD картой не целесообразна т.к велика вероятность выхода её из строя, процесс поиска информации (при большом количестве ключей) будет очень медленным и чем позже записан ключ тем дольше ждать, а сам процесс удаленного обновления данных на SD карте содержит ряд подводных камней.

PS: Я буду настаивать на проведение Вами этого эксперимент.

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


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

Все никак руки не дойдут до теста....
А кто подскажет... а если на меге построить СКУД, сколько она потянет ключей?

 

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


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

@valdemar посчитать объем полезной нагрузки в ключах можно разделив размер eeprom микроконтроллера в байтах на длину используемого ключа. На данный момент мне известны ключи с длинной в 4 и 6 байт. Условившись, что из 6 байтовых ключей будут использованы только первые 4 байта, получаем порядка 1000 ключей для ATmega2560 c 4кб eeprom. Часть памяти придется зарезервировать под значение представляющее количество записанных ключей. Если избавиться от этого значения и не заносить весь перечень ключей в RAM, перебирая ключи непосредственно в eeprom, получим 1024 ключа.

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


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

Kitsum прошу помощи.  Вместо электромагнита удерживающего дверь будет использоватся  электромеханический замок. Подача напряжения на электромагнит открывает замок, но есть одно но в документации на замок указано что на электромагнит напряжение можно подавать не более 4 сек во избежание повреждения катушки электромагнита. Скетч подходит идеально кроме этого момента. Время удержания реле при открытии я подправил, но что делать с режимом программирования и когда в ЕЕПРОМ нет ключей? В этом режиме дверь постоянно открыта. Решение вижу в том чтобы добавить функцию которая будет  в этих случаях включать реле раз в 10 сек на 0,5 сек (со временем можно игратся). прошу помощи самому мне не осилить

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


Ссылка на сообщение
Поделиться на других сайтах
03.05.2019 в 23:31, vlulu333 сказал:

Время удержания реле при открытии я подправил, но что делать с режимом программирования и когда в ЕЕПРОМ нет ключей? В этом режиме дверь постоянно открыта.

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

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


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

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

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



Войти сейчас

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

    • Автор: 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 пользователей

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

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