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

Kitsum

Пользователи
  • Публикации

    424
  • Зарегистрирован

  • Посещение

  • Дней в лидерах

    234

Сообщения, опубликованные пользователем Kitsum


  1. В 31.01.2016в09:52, Alex13 сказал:

    А из какого поста заливать код?!? Залил из последнего, ничего не изменилось!

    Из этого

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

    Собрал на столе с инфракрасным датчиком движения. Все работает.


  2. to Alex13

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

    Текущая логика работы (как я её понимаю из всех предыдущих постов):

    1. Проверка подбора ключей работает постоянно и управляет выходами A1 и A2
    2. На пин A0 заведен сенсор (датчик движения, удара и т.п) который также управляет выходами A1 и A2
    3. Если на пин A0 подать 1, то сигнализация активируется (пин A1 будет взведен до тех пор пока не откроют замок, пин A2 будет поднять 1 минуту)
    4. Если на пин A0 подать 0, но ничего не происходит.

    Остальная логика осталась без изменения. Напоминаю об инверсии выходов A1 и A2

     

     


  3. to Alex13

    Видимо изначально я не так понял логику работы дополнительного входа. Оставляем тот же вход A0.

    Скрытый текст
    
    /*
     * Программа управления нагрузкой с помощью 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
    
    #define PIN_MODE       8  // MODE
    
    #define PIN_ALARM      14 // ALARM ON/OFF
    #define PIN_ALARM_1M   15 // ALARM SIGNAL 1M
    #define PIN_ALARM_LONG 16 // ALARM SIGNAL LONG TIME         
    
    // Инициализация RFID ридера
    MFRC522 mfrc522(PIN_SS, PIN_RST);
    
    // Переменные необходимые для работы со списком ключей
    byte **keys;
    byte keys_count = EEPROM.read(0);
    
    // Переменные необходимые для режима программирования
    byte modeProgTime           = 5;     // Количество секунд удержания мастер ключа для входа\выхода в\из режим\а программирования
    bool mode                   = false; // НЕ МЕНЯТЬ!
    bool modeLock               = false; // НЕ МЕНЯТЬ!
    byte modeClean              = 0;     // НЕ МЕНЯТЬ!
    unsigned long modeTimer     = 0;     // НЕ МЕНЯТЬ!
    unsigned long resetTimer    = 0;     // НЕ МЕНЯТЬ!
    
    // Переменные для сигнализации
    bool invert                 = true;  // Инвертировать выходы сигнализации (A1, A2)
    byte alarmCount             = 0;     // НЕ МЕНЯТЬ!
    unsigned long alarmTimer    = 0;     // НЕ МЕНЯТЬ!
    unsigned long alarmInterval = 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 + "\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);   
    
      // Перемычка выбора режима работы (0 - автоматическое закрытие двери, 1 - закрытие двери по ключу)
      pinMode(PIN_MODE, INPUT);
      digitalWrite(PIN_MODE, HIGH);
    
      // Переменные для сигнализации
      pinMode(PIN_ALARM, INPUT_PULLUP);
      digitalWrite(PIN_ALARM, HIGH);
    
      pinMode(PIN_ALARM_1M, OUTPUT);
      digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
    
      pinMode(PIN_ALARM_LONG, OUTPUT);
      digitalWrite(PIN_ALARM_LONG, invert ? HIGH : LOW);
    
      // Инициализация консоли
      Serial.begin(9600);
      while (!Serial);
      // Приглашаем в гости
      Serial.println(F("iT4iT CLUB (C) 2015\nhttps://it4it.club\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!\n"));
        digitalWrite(PIN_RELAY, LOW);
      }
    }
    /*
      Лупаем, что происходит
    */
    void loop() {
      // Сбрасываем сторожевой таймер микроконтроллера
      wdt_reset();
    
      if(alarmInterval > millis()+10000) alarmInterval = 0;
      if(alarmTimer > millis()+10000) alarmTimer = 0;
      if(resetTimer > millis()+10000) resetTimer = 0;
      if(openTimer > millis()+10000) openTimer = 0;
       
      // Изменение режима работы закрытия
      modeLock = !digitalRead(PIN_MODE);
      // Активация сигнализации от входа A0
      if(digitalRead(PIN_ALARM) and alarmTimer == 0) {
        alarmTimer = millis()/1000;
        digitalWrite(PIN_ALARM_1M, invert ? LOW : HIGH);
        digitalWrite(PIN_ALARM_LONG, invert ? LOW : HIGH);
        Serial.println(F("Attention! Reported alarm from an external sensor!\n"));
      }
      // Очистка памяти
      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("\nMemory cleaning is completed\n"));
            delay(1000);
            resetFunc();
          }
        }
      }
      else if(resetTimer != 0) resetTimer = 0;
      // Открытие двери с кнопки
      key_open.update();
      if(key_open.read() == LOW and openTimer == 0) {
        if(keys_count > 0) {
          if(modeLock or (!modeLock and digitalRead(PIN_RELAY) == HIGH)) {
            openTimer = millis()/1000;
            digitalWrite(PIN_RELAY, LOW);
            Serial.println(F("The door opened from the inside\n"));
            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(alarmTimer != 0) {
        if(millis()/1000 - alarmTimer > 60) {
          alarmTimer = 0;
          digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
          Serial.println(F("Automatic shutdown of the first signaling channel.\n"));
        }
      }
      // Сброс счетчика подбора ключа
      if(alarmInterval != 0) {
        if(millis()/1000 - alarmInterval > 60) {
          alarmInterval = 0;
          alarmCount = 0;
          Serial.println(F("Time selection key expired.\n"));
        }
      }
      // Автоматическое закрытие двери
      if(openTimer != 0) {
        if(millis()/1000 - openTimer > 5) {
          openTimer = 0;
          if(modeLock) {
            digitalWrite(PIN_RELAY, HIGH);
            Serial.println(F("* closed lock\n"));
          }
          else {
            if(digitalRead(PIN_RELAY) == LOW) digitalWrite(PIN_RELAY, HIGH);
          }
        }
      }
      // Если ключ отсутствует или не читается, не выполняем дальнейший код
      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) {
        // Доступ разрешен
        if(modeLock) {
          openTimer = millis()/1000;
          digitalWrite(PIN_RELAY, LOW); 
        }
        else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
        alarmInterval = alarmTimer = alarmCount = 0;
        if((invert and (!digitalRead(PIN_ALARM_1M) or !digitalRead(PIN_ALARM_LONG))) or (!invert and (digitalRead(PIN_ALARM_1M) or digitalRead(PIN_ALARM_LONG)))) {
          digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
          digitalWrite(PIN_ALARM_LONG, invert ? HIGH : LOW);
          Serial.println(F("Clear active alarms signaling\n"));
        }
        uidPrint(F("access allow"));
        squeaker(2, 2200, 200, 200);
        delay(2000);
      }
      else if(!access and !mode and !master) {
        if(alarmInterval == 0) alarmInterval = millis()/1000;
        if(++alarmCount >= 3) {
          if(alarmCount > 3) alarmCount = 3;
          alarmTimer = millis()/1000;
          digitalWrite(PIN_ALARM_1M, invert ? LOW : HIGH);
          digitalWrite(PIN_ALARM_LONG, invert ? LOW : HIGH);
          Serial.println(F("Attention! Attempt selection key! Activate both signaling channel!\n"));
        }
        // Доступ запрещен
        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) {
        alarmInterval = alarmTimer = alarmCount = 0;
        if((invert and (!digitalRead(PIN_ALARM_1M) or !digitalRead(PIN_ALARM_LONG))) or (!invert and (digitalRead(PIN_ALARM_1M) or digitalRead(PIN_ALARM_LONG)))) {
          digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
          digitalWrite(PIN_ALARM_LONG, invert ? HIGH : LOW);
          Serial.println(F("Clear active alarms signaling\n"));
        }
        // Мастер ключ в обычном режиме
        if(modeTimer == 0) {
          modeTimer = millis()/1000;
          if(!mode) {
            if(modeLock) {
              openTimer = millis()/1000;
              digitalWrite(PIN_RELAY, LOW); 
            }
            else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
            // Сигнал о наличии мастер ключа в обычном режиме
            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
        }
        // Мастер ключ удерживается у ридера
      }
    }

     

     


  4. 14 часа назад, Alex13 сказал:

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

    Да, это два условия активации сигнализации.

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

    Получается в моём случае, например А0, подтягиваем к минусу через резистор(10 кОм достаточно?), и если подаем туда высокий логический уровень, происходит мгновенный запуск выходов?

    Резистор не требуется

    PIR_Z1.thumb.png.7342aa2a7ca4d562f3d0e87


  5. Скрытый текст
    3 минуты назад, Alex13 сказал:

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

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

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

     

    Все верно, мы друг друга прекрасно поняли. Только подавать +5v необходимо на A0. На схеме на нем висит двухпозиционный переключатель чтобы показать принцип работы. Выкидываем переключатель и место него используем что угодно от банальной перемычки до GSM сигнализации. Главное, если логика управления идет с другого оборудования, то обязательно объедините GND устройств.


  6. to Alex13

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

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

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

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

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

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

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

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

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

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


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

    Необходимо выбросить лишнее и сэкономить крошки на ATmega328p устанавливаемой в Arduino UNO / NANO

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

    Sketch uses 15 908 bytes (49%) of program storage space. Maximum is 32 256 bytes.
    Global variables use 524 bytes (25%) of dynamic memory, leaving 1 524 bytes for local variables. Maximum is 2 048 bytes

    В ходе быстрого кромсания выкидываем: библиотеку Base64, функцию auth_update() и все переменные, связанные с авторизацией. Хочу заметить, что изначально подразумевалось все это использовать для возможности изменять учетные данные в Web морде и хранить их в EEPROM. В угоду экономии места логин и пароль будет жестко задан в программе.

    Скрытый текст
    
    /*
      sign in (authentication) to the Web server Arduino
      (с) http://it4it.club
    */
    #include <SPI.h>
    #include <Ethernet.h> // Used for Ethernet
    /*
      Настройки сети
    */
    byte mac[] = { 0x54, 0x34, 0x41, 0x30, 0x30, 0x32 };
    IPAddress ip(10, 10, 10, 11);                        
    
    EthernetServer server(80);
    /*
      Переменные для авторизации через web
    */
    String readString; // Буфер для данных от пользователя Web сервера
    /*
      SETUP
    */
    void setup() {
      Serial.begin(9600);
      // Поднимаем сеть  
      Ethernet.begin(mac, ip);
      server.begin();
      Serial.println(Ethernet.localIP());
    }
    /*
      LOOP
    */
    void loop() {
      EthernetClient client = server.available();
      if (client) {  
        boolean currentLineIsBlank = true;
        while (client.connected()) {
          if (client.available()) {
            char c = client.read();
            readString += c;
            if (c == '\n' && currentLineIsBlank) {
              if (readString.lastIndexOf(F("Authorization: Basic bG9naW46cGFzc3dvcmQ="))>-1) {
                if (readString.lastIndexOf(F("GET /favicon.ico"))>-1) {
                  client.println(F("HTTP/1.0 404 Not Found"));
                }
                else {
                  client.println(F("HTTP/1.0 200 OK"));
                  client.println(F("Content-Type: text/html"));
                  client.println(F("Pragma: no-cache\r\nRefresh: 30\r\n"));
                  client.println(F("<html><head><meta charset=UTF-8\"><title>Arduino - iT4iT.CLUB</title>"));
                  client.println(F("<style>body,table,tr,td{font-style:normal;font-family:verdana;font-size:11px;}body{background-color:#FFEBD5;}</style></head><body>"));
                  client.print(F("Arduino on <a href=\"https://it4it.club\">iT4iT.CLUB</a>"));
                  client.println(F("</body></html>"));
                }
              }
              else {
                client.println(F("HTTP/1.0 401 Unauthorized"));
                client.println(F("WWW-Authenticate: Basic realm=\"Arduino - iT4iT.CLUB\""));
              }
              break;
            }
    
            if (c == '\n') {
              currentLineIsBlank = true;
            }
            else if (c != '\r') {
              currentLineIsBlank = false;
            }
          } 
        }
        delay(30);
    
        readString = "";
        client.stop();
      }
    }

     

    Проверка авторизации осуществляется в 40 строке кода, в неё же зашит хэш Base64 - login:password

    if (readString.lastIndexOf(F("Authorization: Basic bG9naW46cGFzc3dvcmQ="))>-1) {

    После повторно компилируем программу и сравниваем вес.

    Sketch uses 14 756 bytes (45%) of program storage space. Maximum is 32 256 bytes.
    Global variables use 414 bytes (20%) of dynamic memory, leaving 1 634 bytes for local variables. Maximum is 2 048 bytes

    Получаем экономию:

    1. Скетч уменьшился на 4%, что составляет 1152 байта
    2. Оперативка освободилась на 5%, что составляет 110 байт

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

    Напоминаю, что логин и пароль пишутся одной строкой через разделитель двоеточие ":". Если для примера взять логин Admin, а пароль СhIcKeN, то должна получится строка Admin:СhIcKeN

    Пишем её в наш online конвертер Base64 и получаем на выходе необходимый нам хеш QWRtaW46Q2hJY0tlTg==

    Base64 online converter

    PS: идея онлайн конвертера Base64 взята с сайта http://webtoolkit.info, в связи с этим я обязан разместить на них ссылку.

    • Like 2

  8. to Alex13

    Необходимо протестировать некоторое время на стабильность работы.

    1. Все реле в схеме можно выбросить и использовать только как сигнальную логику 5v. Например, GSM сигнализация и т.д. ВАЖНО: логика для управления реле инвертирована: 1 - реле закрыто, 0 - реле открыто. Это связанно с особенностями работы релейных модулей поставляемых братьями Китайцами.
    2. Все переключатели можно выкинуть из схемы и использовать как логические входа 5v для связки с другим оборудованием. Единственное условие, чтобы входа изменяющие поведение контроллера (A0, D8) были притянуты к GND или 5v и не висели в воздухе.
    3. Кнопка открытия двери (D2) по умолчанию подтянута к GND, а кнопка сброса памяти (D4) к 5V. ВАЖНО: Если инвертировать сигналы, то дверь будет постоянно открываться или постоянно очищаться память контроллера!

    nrf_z1.2.thumb.png.6769cd8efa371ca4580bf

    Стандартный режим работы описан в первом посте. Хочу только дополнить о доработках:

    1. Если пин D8 подтянут к GND, то дверь закрывается автоматически через 5 сек. после открытия. Если притянут к 5v, то для закрытия замка необходимо повторно поднести к RFID сканеру любой из записанных в памяти ключей.
    2. Если используется RFID метка для закрытия двери, то в закрытом состоянии кнопка открытия остается задействованной и позволит открыть замок изнутри с последующим автоматическим закрытием двери через 5 сек. Вы сможете впустить человека, закрыть за ним дверь, используя метку, и при этом он сможет самостоятельно выйти из помещения.
    3. Если пин A0 подтянут к GND, то сигнализация находится в активном состоянии и работает в предложенном Вами режиме. Если в течении 1 минуты зафиксированы 3 попытки подбора ключа, то на пинах A1 и A2 появляется низкий уровень. Через 1 минуты пин A1 возвращается в исходное состояние и на нем висит высокий уровень. Пин A2 удерживает низкий уровень до тех пор, пока замок не будет открыт одним из известных ему ключей.
    4. Если пин A0 подтянут к 5v, то сигнализация отключается.
    5. Во время открытия замка все счетчики и таймеры связанные с сигнализацией обнуляются.

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

    Сам листинг программы

    Скрытый текст
    
    /*
     * Программа управления нагрузкой с помощью 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
    
    #define PIN_MODE          8        // MODE
    
    #define PIN_ALARM         14       // ALARM ON/OFF
    #define PIN_ALARM_1M      15       // ALARM SIGNAL 1M
    #define PIN_ALARM_LONG    16       // ALARM SIGNAL LONG TIME         
    
    // Инициализация RFID ридера
    MFRC522 mfrc522(PIN_SS, PIN_RST);
    
    // Переменные необходимые для работы со списком ключей
    byte **keys;
    byte keys_count = EEPROM.read(0);
    
    // Переменные необходимые для режима программирования
    byte modeProgTime           = 5;     // Количество секунд удержания мастер ключа для входа\выхода в\из режим\а программирования
    bool mode                   = false; // НЕ МЕНЯТЬ!
    bool modeLock               = false; // НЕ МЕНЯТЬ!
    byte modeClean              = 0;     // НЕ МЕНЯТЬ!
    unsigned long modeTimer     = 0;     // НЕ МЕНЯТЬ!
    unsigned long resetTimer    = 0;     // НЕ МЕНЯТЬ!
    
    // Переменные для сигнализации
    bool invert                 = true;  // Инвертировать выходы сигнализации (A1, A2)
    bool alarm                  = false; // НЕ МЕНЯТЬ!
    byte alarmCount             = 0;     // НЕ МЕНЯТЬ!
    unsigned long alarmTimer    = 0;     // НЕ МЕНЯТЬ!
    unsigned long alarmInterval = 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 + "\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);   
    
      // Перемычка выбора режима работы (0 - автоматическое закрытие двери, 1 - закрытие двери по ключу)
      pinMode(PIN_MODE, INPUT);
      digitalWrite(PIN_MODE, HIGH);
    
      // Переменные для сигнализации
      pinMode(PIN_ALARM, INPUT_PULLUP);
      digitalWrite(PIN_ALARM, HIGH);
    
      pinMode(PIN_ALARM_1M, OUTPUT);
      digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
    
      pinMode(PIN_ALARM_LONG, OUTPUT);
      digitalWrite(PIN_ALARM_LONG, invert ? HIGH : LOW);
      
      // Инициализация консоли
      Serial.begin(9600);
      while (!Serial);
      // Приглашаем в гости
      Serial.println(F("iT4iT CLUB (C) 2015\nhttps://it4it.club\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!\n"));
        digitalWrite(PIN_RELAY, LOW);
      }
    }
    /*
      Лупаем, что происходит
    */
    void loop() {
      // Сбрасываем сторожевой таймер микроконтроллера
      wdt_reset();
    
      if(alarmInterval > millis()+10000) alarmInterval = 0;
      if(alarmTimer > millis()+10000) alarmTimer = 0;
      if(resetTimer > millis()+10000) resetTimer = 0;
      if(openTimer > millis()+10000) openTimer = 0;
       
      // Изменение режима работы закрытия
      modeLock = !digitalRead(PIN_MODE);
      // Изменение режима работы сигнализации
      alarm    = !digitalRead(PIN_ALARM);
      // Очистка памяти
      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("\nMemory cleaning is completed\n"));
            delay(1000);
            resetFunc();
          }
        }
      }
      else if(resetTimer != 0) resetTimer = 0;
      // Открытие двери с кнопки
      key_open.update();
      if(key_open.read() == LOW and openTimer == 0) {
        if(keys_count > 0) {
          if(modeLock or (!modeLock and digitalRead(PIN_RELAY) == HIGH)) {
            openTimer = millis()/1000;
            digitalWrite(PIN_RELAY, LOW);
            Serial.println(F("The door opened from the inside\n"));
            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(alarmTimer != 0) {
        if(millis()/1000 - alarmTimer > 60) {
          alarmTimer = 0;
          digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
          Serial.println(F("Automatic shutdown of the first signaling channel.\n"));
        }
      }
      // Сброс счетчика подбора ключа
      if(alarmInterval != 0) {
        if(millis()/1000 - alarmInterval > 60) {
          alarmInterval = 0;
          alarmCount = 0;
          Serial.println(F("Time selection key expired.\n"));
        }
      }
      // Автоматическое закрытие двери
      if(openTimer != 0) {
        if(millis()/1000 - openTimer > 5) {
          openTimer = 0;
          if(modeLock) {
            digitalWrite(PIN_RELAY, HIGH);
            Serial.println(F("* closed lock\n"));
          }
          else {
            if(digitalRead(PIN_RELAY) == LOW) digitalWrite(PIN_RELAY, HIGH);
          }
        }
      }
      // Если ключ отсутствует или не читается, не выполняем дальнейший код
      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) {
        // Доступ разрешен
        if(modeLock) {
          openTimer = millis()/1000;
          digitalWrite(PIN_RELAY, LOW); 
        }
        else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
        if(alarm) {
          alarmInterval = alarmTimer = alarmCount = 0;
          if((invert and (!digitalRead(PIN_ALARM_1M) or !digitalRead(PIN_ALARM_LONG))) or (!invert and (digitalRead(PIN_ALARM_1M) or digitalRead(PIN_ALARM_LONG)))) {
            digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
            digitalWrite(PIN_ALARM_LONG, invert ? HIGH : LOW);
            Serial.println(F("Clear active alarms signaling\n"));
          }
        }
        uidPrint(F("access allow"));
        squeaker(2, 2200, 200, 200);
        delay(2000);
      }
      else if(!access and !mode and !master) {
        if(alarm) {
          if(alarmInterval == 0) alarmInterval = millis()/1000;
          if(++alarmCount >= 3) {
            if(alarmCount > 3) alarmCount = 3;
            alarmTimer = millis()/1000;
            digitalWrite(PIN_ALARM_1M, invert ? LOW : HIGH);
            digitalWrite(PIN_ALARM_LONG, invert ? LOW : HIGH);
            Serial.println(F("Attention! Attempt selection key! Activate both signaling channel!\n"));
          }
        }
        // Доступ запрещен
        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(alarm) {
          alarmInterval = alarmTimer = alarmCount = 0;
          if((invert and (!digitalRead(PIN_ALARM_1M) or !digitalRead(PIN_ALARM_LONG))) or (!invert and (digitalRead(PIN_ALARM_1M) or digitalRead(PIN_ALARM_LONG)))) {
            digitalWrite(PIN_ALARM_1M, invert ? HIGH : LOW);
            digitalWrite(PIN_ALARM_LONG, invert ? HIGH : LOW);
            Serial.println(F("Clear active alarms signaling\n"));
          }
        }
        // Мастер ключ в обычном режиме
        if(modeTimer == 0) {
          modeTimer = millis()/1000;
          if(!mode) {
            if(modeLock) {
              openTimer = millis()/1000;
              digitalWrite(PIN_RELAY, LOW); 
            }
            else digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
            // Сигнал о наличии мастер ключа в обычном режиме
            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
        }
        // Мастер ключ удерживается у ридера
      }
    }

     

     

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

    rfid_z2.2.thumb.jpg.3cba9b249da37305b31c

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

    PS: Если вдруг Вы будите использовать электромагнит в связке с реле, позаботьтесь о самоиндукции в цепи между ними т.к. в противном случае через некоторое время реле выйдет из строя. Это из опыта эксплуатации моих коллег.

    • Like 1

  9. Скрытый текст
    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 + ... 


  10. Скрытый текст
    4 часа назад, svchekalin сказал:

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

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

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

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

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


  11. Скрытый текст
    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 


  12. Установка драйверов USBasp для Windows 10 / 8.1 / 8

    usbasp-driver.png.4e6331c712f4eb0019b725

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

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

    Теперь нажимаем и удерживая клавишу SHIFT отправляем компьютер на перезагрузку. Это заставит Windows выйти в режим восстановления.

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

    Альтернативный вариант

    1. Нажимаем сочетание клавиш WIN + I для появления окна параметров системы
    2. Выбираем пункт "Обновление и безопасность"
    3. Выбираем раздел "Восстановление"
    4. В параграфе "Особые варианты загрузки" жмем кнопку "Перезагрузить сейчас"

    usbasp-driver-0.thumb.png.2f178ff77928eb

    usbasp-driver-1.thumb.png.55776801c3d086

    После этого мы попадаем в режим восстановления. Выбираем "Поиск и устранение неисправностей".

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

    usbasp-driver-2.thumb.png.67bbd4bfe5897c

    Выбираем "Дополнительные параметры".

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

    usbasp-driver-3.thumb.png.78cb3145bcf760

    Выбираем "Параметры загрузки".

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

    usbasp-driver-4.thumb.png.a00af803205e45

    Отправляем компьютер на перезагрузку.

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

    usbasp-driver-5.thumb.png.eec1070a089994

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

    usbasp-driver-6.thumb.png.492e33650734ae

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

    Во время установки драйвера Вы увидите предупреждение об отсутствии электронной подписи. Игнорируем его и продолжаем установку.

    usbasp-driver-7.png.05a7c0cd440cde46c531

    После этих, не хитрых манипуляций, мы получаем установленный в системе USBasp программатор.

    PS: наслаждаемся результатом.

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

    usbasp-driver-8.png.9c1458458912847f944f

     

    • Like 1

  13. to svchekalin

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

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

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

    • Like 1

  14. Скрытый текст
    1 час назад, svchekalin сказал:

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

     

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

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

    1. arduino.cc
    2. arduino.org

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

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


  16. Скрытый текст
    4 часа назад, Alex13 сказал:

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

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

     

    Привет Alex13!

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


  17. converter_in_pdf_0.jpg

    Доброе время суток.

    По долгу службы приходится обслуживать не одну сотню пользователей, все они уникальны, у каждого свои проблемы, но объединяет их всех одна из эталонных - необходимость конвертировать различные графические форматы в PDF. Тривиальная задача, ставь виртуальный принтер и в бой, но есть несколько проблем:

    1. Все должно быть совершенно бесплатно т.к используется на предприятии и лишняя головная боль никому не нужна.
    2. Конвертер должен принимать форматы TIFF, JPEG, PNG, ...
    3. Некоторые готовые продукты не способны конвертировать те или иные форматы, например печать (встроенными средствами windows) формата TIFF на виртуальный принтер doPDF.
    4. Разный уровень подготовки пользователей, от "Смотри, как я умею" до "Смотри как надо". Вследствие чего некоторые люди способны интуитивно и самостоятельно понять, что к чему и под каким соусом это подать, а некоторые начинают процесс конвертирования с набора номера Вашего покорного слуги и его коллег. Естественно есть и промежуточные варианты эволюции с непредсказуемым результатом.
    5. Все должно быть просто и понятно, чтобы разобраться был способен даже ребенок.

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

    Основные болевые точки уже были затронуты выше, есть от чего отталкиваться, осталось только придумать алгоритм:

    1. Пользователь открывает специальную страницу в браузере
    2. Перетаскивает необходимый ему файл в окно
    3. Немного ждет и получает на скачивание готовый PDF

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

    Переходим к реализации

    В моем случае используется:

    1. Linux Ubuntu 14.04
    2. Apache/2.4.7
    3. PHP 5.5.9
    4. Библиотека php5-imagick
    5. mySQL сервер

    ImageMagick не входит в состав стандартных библиотек PHP (по крайней мере в пакете под Ubuntu), а следовательно:

    apt-get install php5-imagick
    service apache2 restart

    Следующим этапом идет создание базы данных. Сказать честно, это вынужденный пункт т.к необходимо возвращать конвертированный файл с именем идентичным оригиналу, а ведь имя может содержать символы из различных алфавитов (например кириллица). И это вызывает определенные трудности. Зато, как побочный эффект, мы получаем возможность вести логи, следовательно, оптимизировать работу скрипта в будущем.

    И так, создаем базу данных converter и таблицу inPDF со следующей структурой:

    CREATE TABLE IF NOT EXISTS `inPDF` (
      `id` varchar(40) NOT NULL,
      `time` int(20) NOT NULL,
      `name` varchar(100) NOT NULL,
      `size` int(10) NOT NULL,
      `ip` varchar(15) DEFAULT NULL,
      `dns` varchar(50) DEFAULT NULL,
      UNIQUE KEY `id` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    Также следует использовать отдельную учетную запись SQL сервера для доступа к б.д.

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

    chmod 0777 ./upload

    Отредактируйте настройки базы данных в файле index.php

    $settings = array(
        'host'  => 'localhost',
        'db'    => 'converter',
        'login' => 'user',
        'pass'  => 'password'
    );

    Пользовательский интерфейс

    Его необходимо реализовать максимально простым и интуитивно понятным. Я представил его себе в виде обычного перетаскивания файла (TIFF, JPEG, PNG, BMP, можно добавить и другие форматы) в браузер, небольшого ожидания и получения на скачивание конвертированного в PDF файла. Никаких настроек, вопросов и лишних кликов мышкой!

    Реализовать подобное поможет jquery. Я не селен в JS, поэтому побродил немного по сети и нашел, в свободном распространении пример реализации Dragon and Drop. После примитивных модификаций получил приемлемый результат.

    converter_in_pdf_1.jpg

    Главное использовать современный браузер. Данное творение проверено в: Chrome, Firefox, Opera.

    PS: Не претендуем на идеал, но как по мне, для первой версии вполне даже пригодно.

    converter.7z

     

    • Like 1

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

    Пример 1

    1. Сеть является домашней
    2. Один маршрутизатор с IP адресом 192.168.0.1
    3. Один свич с IP адресом 192.168.0.2
    4. SNMP Community на обоих устройствах настроен одинаково - public
    Скрытый текст
    
    $domain = '';
    $community = 'public';
    
    $unit = array(
        'home_network' => array(
            'name'   => 'Домашняя сеть Васи Пупкина',
            'router' => '192.168.0.1',
            'switch' => array('192.168.0.2'),
        ),
    );

     

    Пример 2

    1. Сеть эксплуатируется в маленьком офисе
    2. Один маршрутизатор с IP адресом 192.168.0.1
    3. Два свича с IP адресами 192.168.0.2 и 192.168.0.3
    4. SNMP Community на обоих устройствах настроен одинаково - public
    Скрытый текст
    
    $domain = '';
    $community = 'public';
    
    $unit = array(
        'office' => array(
            'name'   => 'Сеть фирмы "Рога и Копыта"',
            'router' => '192.168.0.1',
            'switch' => array('192.168.0.2', '192.168.0.3'),
        ),
    );

     

    Пример 3

    1. Сеть эксплуатируется в небольшом магазине
    2. Один маршрутизатор с IP адресом 192.168.0.1
    3. Два свича с IP адресом 192.168.0.2 и 192.168.0.3
    4. SNMP Community на маршрутизаторе - private
    5. SNMP Community на свичах - public
    Скрытый текст
    
    $domain = '';
    $community = 'public';
    
    $unit = array(
        'small_shop' => array(
            'name'   => 'Магазин артефактов Бабы Нины',
            'router' => '192.168.0.1',
            'switch' => array('192.168.0.2', '192.168.0.3'),
        ),
    
        '192.168.0.1' => array(
            'community' => 'private'
        ),
    );

     

    Пример 4

    1. Сеть эксплуатируется в большом магазине
    2. Два маршрутизатора с IP адресами 192.168.1.1 и 192.168.2.1
    3. Один свич с IP адресом 192.168.1.2
    4. Два свича с IP адресами 192.168.2.2 и 192.168.2.3
    5. SNMP Community везде одинаков - public
    6. Поднят домен с именем youbigstore.com
    Скрытый текст
    
    $domain = '.youbigstore.com';
    $community = 'public';
    
    $unit = array(
        'router1' => array(
            'name'   => 'Помещение персонала к.101',
            'router' => '192.168.1.1',
            'switch' => array('192.168.1.2'),
        ),
    
        'router2' => array(
            'name'   => 'Торговый зал',
            'router' => '192.168.2.1',
            'switch' => array('192.168.2.2', '192.168.2.3'),
        ),
    );

     

    Пример 5

    1. Сеть эксплуатируется в офисном здании (3 этажа), каждый этаж находится в своей подсети. Сервер с установленным Switch Рort Mapping находится за пределами этих сетей, например в соседнем здании.
    2. 1 этаж. Маршрутизатор Cisco 800 с WAN IP адресом 10.10.1.1 и VLAN 192.168.10.0
    3. 1 этаж. Два свича 3Com с IP адресами 192.168.10.2 и 192.168.10.3
    4. 1 этаж. SNMP Community - public1
    5. 2 этаж. Маршрутизатор Cisco 800 c WAN IP адресом 10.10.2.1 и VLAN 192.168.20.0
    6. 2 этаж. Два свича 3Com с IP адресами 192.168.20.2 и 192.168.20.3
    7. 2 этаж. SNMP Community - public2
    8. 3 этаж. Маршрутизатор Cisco 800 c WAN IP адресом 10.10.3.1 и VLAN 192.168.30.0
    9. 3 этаж. Один свич Allied Telesis с IP адресом 192.168.30.2
    10. 3 этаж. SNMP Community - public3
    11. Поднят домен с именем domain.com
    Скрытый текст
    
    $domain = '.domain.com';
    $community = '';
    
    $unit = array(
        # Описываем оборудования 1 этажа
        'floor1' => array(
            'name'   => 'Первый этаж',
            'router' => '10.10.1.1',
            'switch' => array('192.168.10.2', '192.168.10.3'),
        ),
        # Уникальные настройки для оборудования 1 этажа
        '10.10.1.1'    => array('community' => 'public1'),
        '192.168.10.2' => array('community' => 'public1'),
        '192.168.10.3' => array('community' => 'public1'),
        
        # Описываем оборудования 2 этажа
        'floor2' => array(
            'name'   => 'Второй этаж',
            'router' => '10.10.2.1',
            'switch' => array('192.168.20.2', '192.168.20.3'),
        ),
        # Уникальные настройки для оборудования 2 этажа
        '10.10.2.1'    => array('community' => 'public2'),
        '192.168.20.2' => array('community' => 'public2'),
        '192.168.20.3' => array('community' => 'public2'),
        
        # Описываем оборудования 3 этажа
        'floor3' => array(
            'name'   => 'Третий этаж',
            'router' => '10.10.3.1',
            'switch' => array('192.168.30.2', '192.168.30.3'),
        ),
        # Уникальные настройки для оборудования 3 этажа
        '10.10.3.1'    => array('community' => 'public3'),
        '192.168.30.2' => array('community' => 'public3'),
        '192.168.30.3' => array('community' => 'public3'),
    );

     

    Пример 6

    1. Частная сеть с нестандартным оборудованием
    2. Один маршрутизатор Noname с IP адресом 192.168.0.1
    3. SNMP OID atPhysAddress маршрутизатора отвечающий за соответствие MAC - IP имеет вид ".1.3.6.1.2.1.3.1.1.2" (предположим, что это не стандартный OID)
    4. SNMP Community маршрутизатора - private
    5. Один свич Cisco с IP адресом 192.168.0.2
    6. SNMP Community свича - public
    Скрытый текст
    
    $domain = '';
    $community = '';
    
    $unit = array(
        'home1' => array(
            'name'   => 'Частная сеть',
            'router' => '192.168.0.1',
            'switch' => array('192.168.0.2'),
        ),
        # Настройки для маршрутизатора
        '192.168.0.1' => array(
            'community'     => 'private',
            'atPhysAddress' => '.1.3.6.1.2.1.3.1.1.2',
        ),
        # Настройки для свича
        '192.168.0.2' => array(
            'community'     => 'public',
        ),
    );

     

    PS: Если имеются вопросы, не стесняйтесь обращаться, постараемся помочь!

    • Like 1

  19.  

    Обновление по просьбе 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

  20. Update 03.12.2015

    1. Добавлена возможность отображать DNS имена для определенных хостов, это позволит явно и человеко-понятно идентифицировать соответствующие узлы в сети. Отсутствие записи на DNS сервере должно привлечь соответствующее внимание к хосту.
      Скрытый текст

      sw6.thumb.jpg.be5279ee3b621aa294a6db47a4

       

    2. В конфигурацию добавлен параметр $domain позволяющий удалять из DNS имени хоста имя домена. Чтобы из name.domain.com сделать name, необходимо указать $domain = ".domain.com"
    3. Обновлена база производителей сетевого оборудования - oui.txt
    4. Мелки доработки интерфейса.

     

    Update 26.11.2015

    1. Добавлена возможность отображать реальные имена портов (Fa1, Ethernet1/0/1, Port1 ...). Внимание: не все устройства способны передавать данную информацию, в связи с этим оставлены числовые идентификаторы, чтобы в таких случаях иметь систему идентификации.
      Скрытый текст

      sw5.thumb.jpg.dcddec4de97e754ccd995e572e

       

    2. Добавлена возможность описывать в конфигурации настройки для каждого устройства отдельно. Если не использовать эту возможность, то для оборудования будет применена общая конфигурация.
    3. Добавлена возможность описывать в конфигурации уникальные OID-ы для конкретного оборудования аналогично п.2.
    4. Мелкие доработки интерфейса и кода.

     

    sw4.thumb.png.6a856c6e3d9af36c7fe8e2ee6b

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

    1. Linux Ubuntu 14.04
    2. Apache/2.4.7
    3. PHP 5.5.9
    4. Библиотека php5-snmp

    Также само сетевое оборудование (маршрутизаторы и свичи) должно поддерживать работу по протоколу SNMP и соответствовать стандартам ISO.

    Проект разработан отталкиваясь от топологии сети - звезда. Имеются множество отдельных подсетей 192.168.0.0 все они ходят во внешний мир через маршрутизаторы (в моем случае фирмы Cisco). В самих подсетях используются различные свичи, по большей части поддерживающие SNMP v1 и выше.

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

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

    sw1.png.223c16bb9db3c4b0b87ce510be74d07c

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

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

    sw2.thumb.png.b5a20123c13502de0bab8994cc

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

    sw3.thumb.png.88282e0fea5c631f3d66fece65

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

    sw4.thumb.png.6a856c6e3d9af36c7fe8e2ee6b

    Чтобы избежать вопроса "зачем это нужно?" предлагаю Вам задачку. Попробуйте назвать номер порта на свиче к которому подключен компьютер дяди Васи зная лишь его IP. Или еще интереснее - зная лишь производителя его сетевого оборудования. Или вопрос от начальника "Скажи мне, кто подключен к этому свичу, а я пока узнаю, кого нужно искать".

    Раньше это делалось следующим образом. Подключаемся к маршрутизатору и спрашиваем, какой MAC принадлежит интересующему нас IP, пусть это будет 192.168.0.3

    cisco-router#sh arp | include Vlan1
    Internet  192.168.0.1       210   0800.0694.d027  ARPA   Vlan1
    Internet  192.168.0.2       210   0800.bdf0.0010  ARPA   Vlan1
    Internet  192.168.0.3       210   000e.be08.001c  ARPA   Vlan1
    Internet  192.168.0.4       210   000e.42ee.20cf  ARPA   Vlan1
    Internet  192.168.0.5       210   0800.218e.be09  ARPA   Vlan1
    Internet  192.168.0.6         5   20cf.0800.000e  ARPA   Vlan1
    Internet  192.168.0.7         -   001c.0010.000e  ARPA   Vlan1
    Internet  192.168.0.8         6   0010.1fc4.0800  ARPA   Vlan1
    Internet  192.168.0.9         2   d027.0694.0800  ARPA   Vlan1
    ...

    Теперь мы знаем MAC, это 000e.be08.001c. Подключаемся к свичу и просим его показать нам таблицу MAC адресов с сортировкой по конкретному MAC-у.

    cisco-switch#sh mac-address-table | include 000e.be08.001c
    000e.be08.001c          Dynamic       1     FastEthernet21

    Теперь мы знаем, что интересующий нас (или не нас) хост использует порт FastEthernet21

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

    Реализация

    Мы знаем, что и у какого оборудования спрашивать. Так и давайте спрашивать это используя SNMP протокол. Первым делом необходимо настроить Ваше сетевое оборудование, выставить необходимые разрешения и community. Описывать этот пункт нету смысла т.к у Вас своё оборудования, а у дяди Васи совсем другое.

    На сервере, где установлен Apache необходимо доставить библиотеку реализующий работу snmpwalk в PHP

    apt-get install php5-snmp
    service apache2 restart

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

    Редактируем .htaccess

    RewriteEngine On
    Options +FollowSymlinks
    Options -MultiViews
    
    RewriteBase /switch/
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule (.*) index.php?$1 [L]

    Убедитесь, что у Вас подключен модуль mod_rewrite и измените относительный путь указанный в RewriteBase на удобный Вам. Если закинуть каталог switch в корневую веб директории Apach, то вносить изменений в файл не нужно.

    Редактируем index.php

    $url = 'http://'.$_SERVER["HTTP_HOST"].'/switch/';
    
    $domain = '.domain.com';
    
    $community = 'public';
    $unit = array(
        'network_id' => array(
            'name'   => 'Желаемое для отображения имя подсети или объекта где сеть эксплуатируется',
            'router' => '192.168.1.1',
            'switch' => array('192.168.1.2'),
        ),
        'object_name' => array(
            'name'   => 'Желаемое для отображения имя подсети или объекта где сеть эксплуатируется',
            'router' => '192.168.2.1',
            'switch' => array('192.168.2.2', '192.168.2.3', '192.168.2.4'),
        ),
    
        // UPDATE 26.11.2015
        'new_object' => array(
            'name'     => 'new object name',
            'router' => '192.168.3.1',
            'switch' => array('192.168.3.2', '192.168.3.3'),
    
            // Уникальные настройки для маршрутизатора 192.168.3.2
            '192.168.3.1' => array(
                'community' => 'public2',
            ),
    
            // Уникальные настрокий для свича 192.168.3.3
            '192.168.3.3' => array(
                'community' => 'public3',
                'ifName'    => '.1.3.6.1.2.1.31.1.1.1.1',
                'dot1dBasePortIfIndex' => '.1.3.6.1.2.1.17.1.4.1.2',
            ),
        ),
    );

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

    1. $url - содержит http адрес до каталога со скриптом. Измените /switch/ на используемый Вами
    2. network_id, object_name - любой понравившийся идентификатор для подсети. Используйте латиницу и\или цифры и не используйте пробелы.
    3. name - описание для подсети. Оно будет отображаться в списке на главной странице скрипта и в навигационном баре
    4. router - IP адрес маршрутизатора, через который подсеть ходит во внешний мир
    5. switch - список IP адресов свичей используемых в подсети

    Можно производить уникальную конфигурацию для любого сетевого устройства. Для этого в конфигурации нужного объекта необходимо указать адрес устройства и описать массив настроек для него. Можно изменять абсолютно любые существующие значения в скрипте. ВНИМАНИЕ: если Вы не понимаете, за что отвечает та или иная переменная и как устроена эта кухня, лучше обратитесь к нам, и мы Вам обязательно поможем!

    Список рекомендуемых для изменения значений (описание значений имеется в самом скрипте):

    1. community
    2. atPhysAddress
    3. sysDescr
    4. dot1dTpFdbAddress
    5. dot1dTpFdbPort
    6. dot1dBasePortIfIndex
    7. ifName

    Определение производителя по MAC адресу

    Каждому производителю выделяется определенный список MAC адресов для использования в его сетевом оборудовании. Ознакомиться с этим списком можно по адресу http://standards-oui.ieee.org/oui.txt Мы включили этот файл в состав архива, но советуем Вам периодически обновлять его.

    В скрипте используется shell_exec

    $vendor = explode('(hex)', shell_exec("cat ./oui.txt | grep ".str_replace(' ', '-', substr($mac, 0, 8))));

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

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

    PS: Если все сделано правильно, то Вы сможете насладится нашим велосипедом. Проект будет дорабатываться и обрастать всяческими "свистульками". Приятного использования 😃

     

    • Like 1

  21. Доброе время суток.

    После одного из обновлений Windows появились сбои в работе программы. Пришлось заново компилировать все это хозяйство. Плюс решено все-таки начать переводить программу на правильный язык программирования и делать её Мультиплатформеной. По этим причинам выкладываю все исходники и последний собранный вариант с некоторыми мелкими фиксами.

    Итак, как уже упоминалось ранее, программа написана на PHP и, чтобы превратить её в исполняемый файл понадобится среда разработки DevelStudio

    Проект DVS и распакованный проект с дополнительными файлами и собранной программой (в каталоге build) прикреплены к посту. Почти весь код описан в файлах в каталоге scripts. Серверная часть по-прежнему актуальна и описана в предыдущем сообщении.

    ZabbixTrigger.dvs.tar

    ZabbixTrigger.zip

    • Like 2

  22. Доброе время суток.

    Хочу поделиться опытом автоматизации изобретения Джона Уэбстера - Жалюзи, что в переводе с Французского звучит как Ревность! Скажу честно, что мой опыт обуздать это не хитрое устройство имеет как положительный, так и отрицательный результат. И я буду очень рад поделиться им с Вами, дабы Вы не совершали моих ошибок, и результат Вашей работы вызывал настоящую Ревность у всех, кто её видит!

    zh_17.jpg.7f5ea4843e3c45fdab8e4526b94104

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

    Второе, и самое главное (на чем я прокололся), это выбор привода для управления механизмом. Я правильно подумал, что для поворота всех секций необходимо довольно большой крутящий момент и дать его мне может сервопривод. Решено было взять серву MG995 с металлическими шестернями, крутящим моментом 13 кг. на сантиметр и переделать её в сервопривод постоянного вращения.

    Сама процедура модернизация "мускул" проекта очень проста.

    1. Аккуратно разбираем привод (внутри много смазки) и запоминаем его устройство.
    2. На основной шестерни расположен ограничитель хода (простой цилиндр из другого метала, отличается цветом от шестерней), удаляем его (без усилия вытягивается плоскогубцами).
    3. Вынимаем всю электронику и запоминаем расположения контактов переменного резистора необходимого для позиционирования устройства.
    4. Удаляем переменный резистор и заменяем его двумя обычными и равными половине номинала удаленного органа (в моем случае это два резистора по 2.5 кОм). Полагаю, что можно воспользоваться любыми резисторами одинакового номинала до 10 кОм. Но это стоит перепроверить!
    5. Собираем все обратно.

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

    zh_1.jpg.a8fcf84d807d100728736702be98531

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

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

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

     

    zh_2.jpg.ca1a35ac686ce25c7f02e91815c8a54

    zh_3.jpg.6a0feb163bc798b7ddea043c5f5fd53

    zh_4.jpg.300fb1160e84b5d8e72651701e3cabd

     

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

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

    Причина крылась в неидеальности нашего мира. Создать идеальный резистор не подверженный окружающей среде способна только задача из учебника по физике. А в результате, как только в комнате изменялась температура, даже на короткий миг, один из резисторов менял свои свойства. Вот тут и проявлялись те самые 1%, 5%, 10% погрешностей которые обещал производитель. Мириться с этим я не мог!

    Решений было два:

    1. Разобраться в устройстве мозгов сервопривода и заложить эту погрешность в программу микроконтроллера сервы.
    2. Купить заводскую серву постоянного вращения.

    И как Вы угадали, я сделал единственно верный выбор! Заказал новую серву и ею стало произведения Китайского искуства - модель DS04-NFC. Пластиковые шестерни, крутящий момент 5,5 кг. на сантиметр. Взяв её в руки, и сравнив с предшественницей, я отчетливо испытал чувство со столь красивым названием Жалюзи! Да это была Ревность! Как можно променять MG995 на DS04-NFCкаааакккк! Каарррлл скажииии, каааааккк!

    Ладно, боль стихла, выбор сделан. Необходимо проверить её в деле. И забегая вперед, я обязан сказать, что новая серва была подвержена той же болезни, что и переделанная MG995. Фокус не удался, но исправить проблему получилось программно в самом скетче!

     

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

     

    zh_5.thumb.jpg.c8d71e5e595293debd40961c2zh_6.thumb.jpg.e7f2828416ea66b1a6206b472

    zh_7.jpg.8189388ee6c31cd42b2e5dcde5aec1a

     

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

    Для исправления данного недостатка я посадил параллельно нестабилизированного входа Arduino преобразователь AMS-1117-5.0 (Китайский клон LM-1117-5.0). Таким образом, я могу подавать питание (рекомендую 7-9V) на вход платы c контроллером и иметь раздельное стабилизированное питание, как самого контроллера, так и сервопривода. Фокус удался...

    Думаю, что схему параллельного соединения приводить нет смысла. Добавлю лишь обвязку преобразователя (два танталовых конденсатора по 10mF).

    zh_8.jpg.5dd0dc619df32029a768bdadf45cf09

    Для первого раза было решено использовать управление от старого пульта TV-тюнера. Ну, раз пошла такая пляска, то в довесок выдернуть из TV-тюнера IR приемник и гнездо для него. Если быть честным, то я не собирался потрошить тюнер, но один из моих Китайских поставщиков  электроники решил меня обмануть кинуть (скажу по секрету, это был не его день!).

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

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

     

    zh_9.jpg.5cce44fd1ad5bc811af27ca570597c7

    zh_10.jpg.0f14205d09f7e9e174705b60679741

    zh_11.jpg.f41c4bc41e14f71fa94e42394225d2 

     

    Согласен, не очень красиво но, что я могу поделать. Исправлю в следующей версии.

    Переходим к программной части

    По началу стоит определиться с командами, на которые будет реагировать контроллер. Это зависит только от используемого Вами пульта дистанционного управления. За основу я взял библиотеку Arduino-IRremote-master.zip В комплекте куча примеров чтобы научиться с ней дружить.

    Ну а итоговый скетч будет выглядеть следующим образом

    #include <IRremote.h>
    #include <Servo.h> 
    
    byte PIN_IR     = 8;
    byte PIN_SERVO  = 9;
    
    IRrecv irrecv(PIN_IR);
    decode_results ir;
    
    Servo myservo;
    
    unsigned long timer = 0;
    
    unsigned long lSpeed = 0;
    unsigned long rSpeed = 0;
    
    byte Speed;
    int  fullSpeedTime = 3000;  // Время в миллисекундах, за которое серва выходит на полную скорость
    
    void setup()
    {
      Serial.begin(9600); 
      irrecv.enableIRIn();
    }
    
    void loop() {
      if (irrecv.decode(&ir)) {   
        //Serial.println(ir.value);
        
        // Поворот направо
        if(ir.value == 117192113) {
          timer = millis();
    
          if(rSpeed == 0) rSpeed = millis();
          Speed = map(millis()-rSpeed<fullSpeedTime?millis()-rSpeed:fullSpeedTime, 0, fullSpeedTime, 100, 180);
          Serial.println("Servo turns to the right. Speed " + String(Speed));
          
          myservo.attach(PIN_SERVO);
          myservo.write(Speed);
        }
        // Поворот налево
        else if(ir.value == 1819890863) {
          timer = millis();
    
          if(lSpeed == 0) lSpeed = millis();
          Speed = map(millis()-lSpeed<fullSpeedTime?millis()-lSpeed:fullSpeedTime, 0, fullSpeedTime, 100, 0);
          Serial.println("Servo turns to the left. Speed " + String(Speed));
          
          myservo.attach(PIN_SERVO);
          myservo.write(Speed);
        }
    
        irrecv.resume();
      }
      else {
        // Обнуляем все задействованные таймеры и отключаем серву
        if(timer != 0 and millis() - timer > 200) {
          rSpeed = lSpeed = timer = 0;
          myservo.detach(); // <- Именно этот костыль заставляет серву молчать
          Serial.println(F("Servo stop\n"));
        }
      }
    }

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

    Углы поворота влияют на скорость:

    1. от 0 до 100
    2. от 100 до 180

    Это те значения, что получились у меня на двух сервах, и которые я использовал в скетче.

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

    myservo.detach();

    Все прекрасно отработало. Серва крутит, а в простоях молчит и даже не думает дернуться.

    Привожу еще немного снимков всей получившейся конструкции

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

    zh_12.jpg.01382d1ebab231cf53ef5d1d2c44d3zh_13.jpg.530ad14f8ef2b21132959cf5a90ec3zh_14.jpg.903b59243274dad36df3cfbfed628bzh_15.jpg.1a069862217d291cbc04f8cfc9ef0bzh_16.thumb.jpg.51da77fc4abecde7973fcc7a

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

    PS: В общем первая версия была проверена и зарекомендовала себя хорошо, правда выглядит ужасно. Планирую добавить пару датчиков освещенности и автоматически менять угол поворота ламелей в зависимости от освещенности в течении дня. И заменить серву на тихий шаговый двигатель c редуктором.

    • Like 2

  23. squid.thumb.jpg.53bf048f31867ba95a7488ed

    В данной заметке мы рассмотрим, как можно производить мониторинг прокси-сервера Squid через Zabbix сервер по протоколу SNMP. Предполагается, что у Вас уже имеется работоспособный сервер c ip адресом 192.168.0.11

    В моём случае обкатка производилась на: 

    1. Linux Ubuntu 14.04
    2. Squid 3.3.8
    3. Zabbix 2.4

    Дополняем файл конфигурации Squid

    1. Указываем, какие интерфейсы и порт использовать для поднятия SNMP

    snmp_incoming_address 192.168.0.11
    snmp_outgoing_address 192.168.0.11
    snmp_port 2161

    2. Правим ACL. Указываем необходимый community и ip адрес Zabbix сервера.

    acl snmp_monitoring snmp_community public
    acl snmp_client src 192.168.0.2

    3. Пускаем Zabbix к Squid и блокируем всех остальных.

    snmp_access allow snmp_monitoring snmp_client
    snmp_access deny all

    4. Сохраняем изменения в конфиге и проверяем его на отсутствие ошибок.

    squid3 -f /etc/squid3/squid.conf -k parse

    5. Перезапускаем прокси-сервер.

    service squid3 restart

    6. Проверяем, прослушивает ли Squid порт 2161

    netstat -a | grep 2161

    На этом настройка прокси-сервера завершена. На всякий случай оставляю ссылку на сайт разработчика, где указаны различия в разных версиях Squid: http://www.squid-cache.org/Doc/config/

    Сам сервер заботливо оставил нам MIB файл с описанием своей кухни:

    /usr/share/squid3/mib.txt

    После его изучения, можно накидать начальный шаблон для Zabbix  Squid-Template.zip

    Для наблюдаемого узла сети можно добавить два макроса:

    {$SNMP_SQUID_COMMUNITY} = public
    {$SNMP_SQUID_PORT} = 2161

    Это необходимо если Вы хотите изменить настройки community или port. По умолчанию, данные макросы уже указаны в настройках шаблона.

    zabbix_squid_1.thumb.jpg.85a44fc5cad0070

    Список собираемых параметров:

    Squid Config

    1. Cache Administrator E-Mail address
    2. Cache Software Name
    3. Cache Software Version
    4. Cache Swap High Water Mark
    5. Cache Swap Low Water Mark
    6. Cache unique host name
    7. Logging Facility
    8. The total of the cache_dir space allocated in MB
    9. The value of the cache_mem parameter in MB

    Squid Dns

    1. Number of external DNS server processes
    2. Number of external DNS server replies
    3. Number of external DNS server requests

    Squid Fqdn Cache

    1. FQDN Cache entries
    2. Number of blocking gethostbyaddr requests
    3. Number of FQDN Cache hits
    4. Number of FQDN Cache misses
    5. Number of FQDN Cache negative hits
    6. Number of FQDN Cache pending hits
    7. Number of FQDN Cache requests

    Squid Ip Cache

    1. IP Cache Entrie
    2. Number of attempts to release locked IP Cache entrie
    3. Number of blocking gethostbyname requests
    4. Number of IP Cache hits
    5. Number of IP Cache misses
    6. Number of IP Cache negative hits
    7. Number of IP Cache pending hits
    8. Number of IP Cache requests

    Squid Median Svc Table (1/5/60)

    1. Byte Hit Ratios
    2. DNS service
    3. HTTP all service
    4. HTTP hit not-modified service
    5. HTTP hit service
    6. HTTP miss service time
    7. HTTP refresh hit service time
    8. ICP query service time
    9. ICP reply service time
    10. Request Hit Ratios

    SquidSystem

    1. Storage Mem size in KB
    2. Storage Swap size in KB
    3. The Uptime of the cache in timeticks

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

    squid.thumb.png.5490cb704535d8dd3ae7fe58

     

    • Like 1

  24. Продолжаем развивать нашу метеостанцию.

    Перед тем, как перейти к обновлению, хочу внести немного ясности.

    Мне написал один из наших коллег с вопросом, по какой причине введен сторожевой таймер?

    Цитата

    Сторожевой таймер стоит на случай ч.п. Как показывает практика, ENC28J60 не тянет более (если не подводит память) 4 одновременных соединений. Учитывая сколько служебных соединений, постоянно происходит для поддержания работы самой сети, и просто левый трафик, создаваемый всяческими домашними игрушками (например, современные телевизоры, сканируют доступные хосты в сети и открытые у них порты) конструкция попросту уходит в ступор. ENC28J60 не умеет самостоятельно работать с сетевыми протоколами и все реализовано в библиотеках. Возможно дело именно в них. 
    Проверял все доступные библиотеки и разные модули (вдруг брак), но добиться стабильной работы в течении длительного времени у меня не получилось. Максимальный срок был порядка 3-4 недель.
    Именно для этого там крутится "пес" и в случае чего дергает контроллер. После этого проблема ушла.
    Также не отрицаю, что возможно в моей домашней сети есть определенные нюансы или проблемы. Но раз проблема была у меня, она может выплыть и у другого человека. Я пока нашел только такое решение.
    Насколько мне известно, на чипах от Wiznet (W5100 и выше) этого нет, ну или просто плохо искали.

    Переходим к обновлению

    ws1.thumb.jpg.3b8bd2b7f8d6df5e2ce091a9ea

    ws2.thumb.jpg.9867ee032b43afc4b410e85cc5

    Самое главное, мы уходим от чипа ENC28J60 и переходим на W5100. Я пытался реализовать все на старом чипе, но не хватает памяти микроконтроллера из-за очень больших библиотек для ENC28J60. При использовании нового чипа, стандартной библиотеки от разработчика и всех внесенных изменений, остается еще более 20% свободной памяти микроконтроллера ATMega328. А это, новые плюшки!

    В этой версии (назовем её второй) добавлена возможность передачи показаний с датчиков по беспроводной связи используя частоту 433 мГц. Сами модули я брал у Китайцев, маркировка XY-MK-5V. Хочу отметить, что качество передачи далеко от совершенства. Возможны потери сигнала, шумы, не возможность одновременной передачи и т.д и т.п. Но их цена (менее $1 за комплект) компенсируют эти недостатки. Скажу Вам по секрету, что именно эти (самые дешевые) модули стоят во многих фирменных метеостанциях для домашнего использования. Ого, неожиданно?

    Начнем с базовой станции

    Мы переходим на Arduino UNO и Ethernet Shield (первой версии) на базе чипа W5100. Это бутерброд и описывать его нету смысла. Я опишу только дополнительно задействованные контакты для модулей XY-MK-5V.

    Модуль передатчика использует питание 5V, GND (куда без матушки то) и D2 пин на контроллере. Изменить контакт D2 (DATA) можно, используя функцию vw_set_tx_pin из библиотеки vw.

    В отличии от предыдущего скетча, в этом задействованы две дополнительные библиотеки:

    #include <VirtualWire.h>
    #include <EasyTransferVirtualWire.h>
    1.  VirtualWire.zip
    2.  EasyTransferVirtualWire.zip

    PayPal.png

    Сам скетч

    Скрытый текст
    
    #include <avr/wdt.h>
    #include <Wire.h>
    #include <BMP085.h>
    #include <DHT.h>
    #include <SPI.h>
    #include <Ethernet.h>
    #include <VirtualWire.h>
    #include <EasyTransferVirtualWire.h>
    
    #define DHTTYPE DHT22
    #define DHTPIN 5
    
    DHT dht(DHTPIN, DHTTYPE);
    
    byte mac[] = {0x54, 0x34, 0x31, 0x31, 0x31, 0x31};
    char server[] = "narodmon.ru";
    int port = 8283;
    
    IPAddress ip(192,168,0,201);
    EthernetClient client;
    
    BMP085 dps = BMP085();
    long Temperature = 0, Pressure = 0;
    float H, dP, dPt;
    
    bool interval = true;
    
    EasyTransferVirtualWire ET;
    struct SEND_DATA_STRUCTURE{
       byte ID;           // Идентификатор устройства
        int Temperature;  // Температура
      float Pressure;     // Давление
      float Humidity;     // Влажность
      float dewPoint;     // Точка росы/инея
    };
    SEND_DATA_STRUCTURE broadcast;
    
    void setup() {
      // Инициализация сторожевого таймера (Watchdog timer)
      wdt_disable();
      delay(8000);
      wdt_enable(WDTO_8S);
      // Инициализация консоли
      Serial.begin(9600);
      // Инициализация датчика DHT
      dht.begin();
    
      // Инициализация модуля 433 мГц
      ET.begin(details(broadcast));
      vw_set_ptt_inverted(true);
      vw_set_tx_pin(2);
      vw_setup(2000);
      
      // Стартуем сеть, если не дождались данных с DHCP сервера то
      // присваеваем себе адрес самостоятельно
      if (Ethernet.begin(mac) == 0) Ethernet.begin(mac, ip);
      // Инициализация 1-Wire
      Wire.begin();
      delay(200);
      
      // Инициализация BMP180 с корректировкой высоты
      // dps.init(MODE_STANDARD, 3200, true);
      
      // Инициализация BMP180
      dps.init();
     
      Serial.println(Ethernet.localIP());
      
      // Отправляем первые данные сразу после включения устройства
      send_info(true);
    }
    // dewPoint function NOAA
    // reference (1) : http://wahiduddin.net/calc/density_algorithms.htm
    // reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm
    double dewPoint(double celsius, double humidity)
    {
      // (1) Saturation Vapor Pressure = ESGG(T)
      double RATIO = 373.15 / (273.15 + celsius);
      double RHS = -7.90298 * (RATIO - 1);
      RHS += 5.02808 * log10(RATIO);
      RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ;
      RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ;
      RHS += log10(1013.246);
      // factor -3 is to adjust units - Vapor Pressure SVP * humidity
      double VP = pow(10, RHS - 3) * humidity;
      // (2) DEWPOINT = F(Vapor Pressure)
      double T = log(VP/0.61078);   // temp var
      return (241.88 * T) / (17.558 - T);
    }
    
    void send_info(bool eth) {
      bool fail = true;
    
      while(fail) {
        // Пытаемся считать данные с датчика влажности DHT до тех пор, пока не получим 
        // результат. В 90% случаев все работает нормально, но нам нужны 100%
        if((H = dht.readHumidity()) >= 0) {
          // Получение влажности и температуры с датчика BMP180
          dps.getPressure(&Pressure);
          dps.getTemperature(&Temperature);
          // Подсчитываем точку росы, если температура на улице выше 0 градусов Цельсия
          // и ожидаем результат выше 0, в противном случае выводим 0. Это необходимо
          // чтобы не вводить в заблуждения в зимее время года.
          // dP = Temperature>0?((dPt=dewPoint(Temperature*0.1, H))<0?0:dPt):0;
          dP = dewPoint(Temperature*0.1, H);
    
          // Отправляем данные в эфир 433 мГц
          broadcast.ID = 1;
          broadcast.Temperature = floor(Temperature*0.1);
          broadcast.Pressure    = floor(Pressure/133.3*10)/10;
          broadcast.Humidity    = floor(H*10)/10;
          broadcast.dewPoint    = floor(dP*10)/10;
          ET.sendData();
          delay(250);
    
          if(eth) {
            // Подключаемся к серверу "Народный мониторинг"
            if(client.connect(server, port)) {
              // Начинаем передачу данных
              // адрес_устройства_в_проекте, имя_устройства, GPS широта, GPS долгота
              client.print(F("#fe-31-31-0e-5a-3b#Arduino Uno#71.344699#27.200014\n"));
              // Температура
              client.print(F("#T0#"));
              client.print(Temperature*0.1);        
              client.print(F("#Температура\n"));
              // Давление
              client.print("#P1#");
              client.print(Pressure/133.3);
              client.print(F("#Давление\n"));
              // Влажность
              client.print("#H1#");
              client.print(H);
              client.print(F("#Влажность\n"));
              // Точка росы\инея
              client.print("#T1#");
              client.print(dP);
              client.print((dP <= 0)? F("#Точка инея\n"):F("#Точка росы\n"));
              //client.print(F("#Точка росы\n"));
              // Отправляем конец телеграммы
              client.print("##");
            
              // Даем время отработать Ethernet модулю и разрываем соединение
              delay(250);
              client.stop();
            }
          }
          // Останавливаем цикл, если передача завершена
          fail = !fail;
          break;
        }
        delay(250);
      }
    }
    
    void loop()
    {
      // Каждые 4 секунды сбрасываем сторожевой таймер микроконтроллера
      // Каждые 6 минут отправляем данные на "Народный мониторинг"
      // Каждые 30 секунд отсылаем данные в эфир 433
      if(!(millis()%1000)) wdt_reset();
      if(!(millis()%360000)) send_info(true);
      if(!(millis()%30000)) send_info(false);
    }
    

     

    К самим модулям необходимо добавить антенну.  Для 433 мГц достаточно обычного медного провода длинной 17 см. Без антенны можете забыть о нормальной работе.

    Переходим к самой важной части этого обновления - локальная беспроводная станция

    Для её реализации (на коленке) я использовал аналог Arduino NANO (на базе ATMega328) и TFT дисплей на чипе ST7735S с разрешением 128 x 160

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

    1.8_B.thumb.JPG.da9ee0de7c73021638755a3e1.8_C.thumb.jpg.51f33277344535ee5c9882a3SKU128815e.thumb.JPG.8fcef552562ebd80298

    Распиновка дисплей -> контроллер

    =============================
    LED	    |	3.3V
    SCK	    |	SCK	    (13)
    SDA	    |	MOSI	(11)
    A0	    |	DC	    (9)
    RESET	|	RST	    (8)
    CS	    |	CS	    (10)
    GND	    | 	GND
    VCC	    |	5V
    ============================

    Модуль приемник подключается также как передатчик, только DATA к пину D7.

    Пару снимков, как это выглядит:

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

    ws3.thumb.jpg.e57c3c7e16a3cdfc71f85b0a59

    ws4.thumb.jpg.8b8d250793e4ad41a0bedee711

    ws5.thumb.jpg.34db8e052a96c34cc668577d40

    ws6.thumb.jpg.d2f783c71d08f1d5b8417922b9

    Скетч приемника

    Скрытый текст
    
    #include <VirtualWire.h>
    #include <EasyTransferVirtualWire.h>
    #include <TFT.h>
    #include <SPI.h>
    
    int x, y;
    int w = 128, h = 160;
    int size;
    
    // 433
    EasyTransferVirtualWire ET;
    struct SEND_DATA_STRUCTURE{
       byte ID;           // Идентификатор устройства
        int Temperature;  // Температура
      float Pressure;     // Давление
      float Humidity;     // Влажность
      float dewPoint;     // Точка росы/инея
    };
    SEND_DATA_STRUCTURE broadcast;
    
      int Log_Temperature = -1;
    float Log_Pressure    = -1;
    float Log_Humidity    = -1;
    float Log_dewPoint    = -1;
    
    // TFT
    #define cs   10
    #define dc   9
    #define rst  8
    
    char Temperature[6], Pressure[9], Humidity[4], dewPoint[6];
    String info;
    
    TFT TFTscreen = TFT(cs, dc, rst);
    
    void setup(){
      Serial.begin(9600);
    
      // Инициализация модуля 433 мГц
      ET.begin(details(broadcast));
      
      vw_set_ptt_inverted(true);
      vw_set_rx_pin(7);
      vw_setup(2000);
      vw_rx_start();
    
      // Инициализация и начальная настройка дисплея
      TFTscreen.begin();
      TFTscreen.setRotation(2);
      TFTscreen.background(0, 0, 0);
      
      // Рисуем статические элементы
      // 1. Заходите к нам в гости
      TFTscreen.stroke(255, 255, 255);
      TFTscreen.setTextSize(1);
      TFTscreen.text("HTTPS://iT4iT.CLUB", 10, 10);
      // 2. Описание показаний с датчиков
      TFTscreen.text("mmHg", w/2+5, 80);
      TFTscreen.text("%", w/2+5, 100);
      TFTscreen.text("C", w/2+5, 120);
      
      broadcast.Temperature = 0;
      broadcast.Pressure    = 0;
      broadcast.Humidity    = 0;
      broadcast.dewPoint    = 0;
    
      TFTPrint();
    }
    
    void loop(){
      if(ET.receiveData()){
        if(broadcast.ID == 1) TFTPrint();
        /*
        Serial.println(broadcast.Temperature);
        Serial.println(broadcast.Pressure);
        Serial.println(broadcast.Humidity);
        Serial.println(broadcast.dewPoint);
        Serial.println();
        */
      }
    }
    
    void changes(int size, int x, int y, bool up, bool clear = false) {
      if(clear) TFTscreen.stroke(0, 0, 0);
      else {
        changes(size, x, y, !up, true);
        TFTscreen.stroke((up)?0:255, 0, (up)?255:0);
      }
      if((size%2) == 0) size++;
      while(size > 0) {
        TFTscreen.line(x, y, x+(size--), y);
        ++x, (up)?--y:++y, --size;
      }
      /*
      while(size > 0) {
        TFTscreen.line(x, y, (up)?x+size-1:x, (up)?y:y+size-1);
        ++x, ++y, --size;
      }
      */
    }
    
    int x_center(int w, int length, int size) {
      return floor((w-length*(size*5)+size*2)/2);
    }
    
    int x_alignment_right(int w, int length, int size) {
      return ceil(w-length*(size*5)+size*2);
    }
    
    void TFTPrint() {  
      size = 3;
    
      // ==================================================================================
      // Вывод показаний температуры
      // ==================================================================================
      if(broadcast.Temperature != Log_Temperature) {
        TFTscreen.setTextSize(size);
       
        // Затираем устаревшие данные
        String info = String(Log_Temperature);
        info.concat(" C");
        if(Log_Temperature > 0) info = '+'+info;
        info.toCharArray(Temperature, info.length()+1);
        TFTscreen.stroke(0, 0, 0);
        TFTscreen.text(Temperature, x_center(w, info.length()+1, size), 35);
        
        // Выводим новые показания
        info = String(broadcast.Temperature);
        info.concat(" C");
        if(broadcast.Temperature > 0) info = '+'+info;
        info.toCharArray(Temperature, info.length()+1);
    
        // Меняем цвет значения температуры в зависимости от самой температуры
        int r, g = 0, b;
        if(broadcast.Temperature > 0) {
          r = map(broadcast.Temperature, 0, 40, 255, 150);  // Красный
          b = map(broadcast.Temperature, 0, 40, 30, 0);     // Изменяем оттенок для более наглядного перехода через ноль
        }
        else {
          r = map(broadcast.Temperature, -40, 0, 0, 30);    // Изменяем оттенок для более наглядного перехода через ноль
          b = map(broadcast.Temperature, -40, 0, 150, 255); // Синий
        }  
        TFTscreen.stroke(b, g, r); // ВНИМАНИЕ: в библиотеке перепутаны позиции цветов, место RGB используется BGR!    
        TFTscreen.text(Temperature, x_center(w, info.length()+1, size), 35);
      }
    
      size = 1;
    
      // ==================================================================================
      // Вывод показаний давления
      // ==================================================================================
      if(broadcast.Pressure != Log_Pressure) {
        TFTscreen.setTextSize(size);
    
        // Затираем устаревшие данные
        info = String(Log_Pressure);
        info.toCharArray(Pressure, info.length());    
        TFTscreen.stroke(0, 0, 0);
        TFTscreen.text(Pressure, x_alignment_right(w/2-5, info.length(), size), 80);
        
        // Выводим новые показания
        info = String(broadcast.Pressure);
        info.toCharArray(Pressure, info.length());
        TFTscreen.stroke(255, 255, 255);
        TFTscreen.text(Pressure, x_alignment_right(w/2-5, info.length(), size), 80);
        
        changes(10, 106, 85, (broadcast.Pressure > Log_Pressure)?true:false);
      }
      else {
        changes(10, 106, 85, true, true);
        changes(10, 106, 85, false, true);
      }
    
      // ==================================================================================  
      // Вывод показаний влажности
      // ==================================================================================
      if(broadcast.Humidity != Log_Humidity) {
        TFTscreen.setTextSize(size);
    
        // Затираем устаревшие данные
        info = String(Log_Humidity);
        info.toCharArray(Humidity, info.length());    
        TFTscreen.stroke(0, 0, 0);
        TFTscreen.text(Humidity, x_alignment_right(w/2-5, info.length(), size), 100);
    
        // Выводим новые показания
        info = String(broadcast.Humidity);
        info.toCharArray(Humidity, info.length());
        TFTscreen.stroke(255, 255, 255);
        TFTscreen.text(Humidity, x_alignment_right(w/2-5, info.length(), size), 100);
    
        changes(10, 106, 105, (broadcast.Humidity > Log_Humidity)?true:false);
      }
      else {
        changes(10, 106, 105, true, true);
        changes(10, 106, 105, false, true);
      }
    
      // ==================================================================================
      // Вывод показаний точки росы\инея
      // ==================================================================================
      if(broadcast.dewPoint != Log_dewPoint) {
        TFTscreen.setTextSize(size);
    
        // Затираем устаревшие данные
        info = String(Log_dewPoint);
        info.toCharArray(dewPoint, info.length());    
        TFTscreen.stroke(0, 0, 0);
        TFTscreen.text(dewPoint, x_alignment_right(w/2-5, info.length(), size), 120);
    
        // Выводим новые показания
        info = String(broadcast.dewPoint);
        info.toCharArray(dewPoint, info.length());
        TFTscreen.stroke(255, 255, 255);
        TFTscreen.text(dewPoint, x_alignment_right(w/2-5, info.length(), size), 120);
    
        changes(10, 106, 125, (broadcast.dewPoint > Log_dewPoint)?true:false);
      }
      else {
        changes(10, 106, 125, true, true);
        changes(10, 106, 125, false, true);
      }
    
      // Обновляем значения в логах для последующего сравнения показаний
      Log_Temperature = broadcast.Temperature;
      Log_Pressure    = broadcast.Pressure;
      Log_Humidity    = broadcast.Humidity;
      Log_dewPoint    = broadcast.dewPoint;
    }
    

     

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

    Как мне показалось, дизайн это та часть проекта, которая отнимает большую часть времени!

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

    ws7.thumb.jpg.9f7cd0164b59785882b8334466ws8.thumb.jpg.ed96f96acab8db15f33e39657dws9.thumb.jpg.99e23f601a4018d32ff966c11aws10.thumb.jpg.f63ab87c09956186c3cff6281

    Часть данных сфабрикованы для отображения некоторых элементов дизайна.

    Артефакты на дисплее, это пыль и прочая грязь скопившаяся за долго время нахождения дисплея в... где то там, ... ну там, не помню откуда его достал! Отстаньте!

    В скетче имеются функции позиционирования. Они довольно примитивны, но позволяют добиться определенных эффектов.

    1. x_center
    2. x_alignment_right

    Первая производит центровку текста, а вторая выравнивание по правой части указанной зоны. Все вычисления производятся относительно размеров заданного текста, исходя из выражения 1 size = 1PX х 1PX сегмента шрифта.

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

    Кстати, цвет и оттенок основной температуры изменяется в зависимости от самой температуры. Довольно спорное решение, но на мой взгляд, визуально комфортное. Я некоторое время бился над ней, и понял, что значения в функции stroke, объекта TFT дисплея, указаны в неверном порядке. BGR место RGB. Это ошибка разработчика, ну или я что-то не понимаю.

    PS: Все довольно интересно, но на мой взгляд заслуживает дальнейшего развития. Чем и займемся через какое то время. 

    EasyTransferVirtualWire.zip

    VirtualWire.zip

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