Arduino: ограничение на время просмотра телевизора с помощью RFID RC522 и

в 11:36, , рубрики: arduino, open source, relay, RFID, ограничение просмотра ТВ

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

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

Для реализации своей идеи использовал:

1. Double Side Prototype PCB Tinned Universal Breadboard 2x8 cm 20mmx80mm FR4:
image

2. 5V One 1 Channel Relay Module Board Shield For PIC AVR DSP ARM MCU Arduino:
image

3. USB Nano V3.0 ATmega328 16M 5V Micro-controller CH340G board For Arduino:
image

4. Mifare RC522 Card Read Antenna RF Module RFID Reader IC Card Proximity Module:
image

5. White 3-5V 0.96" SPI Serial 128X64 OLED LCD LED Display Module for Arduino:
image

При включении устройства оно считывает информацию из EEPROM на наличие записанных карт (максимальное количество карт я ограничил в 6 штук). В EEPROM хранятся последние 4 байта UID'а карты переведенные в десятичный формат. Для чтения и записи в EEPROM использовал библиотеку EEPROM2.h.

Код
cardPresent = readCards();
boolean readCards() {
  cardPresent = false;
  for(int k = 0; k <6; k++) {
    EEPROM_read(k*6+4, time[k]);
    if(time[k] >= 0) {
      cardPresent = true;
      EEPROM_read(k*6, cards[k]);
      }
  }
  return cardPresent;
}

Для работы с модулем MFRC522 использовал библиотеку MFRC522.h. Считываем карты следующим образом:

Код

MFRC522 mfrc522(SS_PIN, RST_PIN);
void setup() {
  SPI.begin();			// Init SPI bus
  mfrc522.PCD_Init();		// Init MFRC522
void loop() {
	// Look for new cards
	if ( ! mfrc522.PICC_IsNewCardPresent()) {
		return;
	}

	// Select one of the cards
	if ( ! mfrc522.PICC_ReadCardSerial()) {
		return;
	}

        String s = dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
        mfrc522.PICC_HaltA(); //останавливаем считывание карты до того момента как ее уберут от считывателя (иначе функция dump_byte_array будет выполняться несколько раз, пока карту не уберут от считывателя).
        Serial.println(s);
}

В случае если карты отсутствуют в EEPROM первая приложенная карта назначается мастером и записывается в ячейку 0:

Код

    if(!cardPresent) { //если карт нет, то записать карту как мастер
      short master = 0;
      EEPROM_write(0, uiddec);
      EEPROM_write(4, master);
      message = "NEW MASTER";
      cardPresent = true;
      readCards();
    }

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

Код
/* Create an instance of the library (uncomment one of the lines below) */
//HCuOLED HCuOLED(SSD1307, SS_DI, DC_DI, RST_DI); // For SSD1307 displays (HCMODU0050 & HCMODU0052)
HCuOLED HCuOLED(SH1106, CS_DI, DC_DI, RST_DI); // For SH1106 displays (HCMODU0058 & HCMODU0059)
void setup() {
     HCuOLED.Reset();
}
void drawUid(char *s) {
        HCuOLED.Erase(0,16,120,32);
        HCuOLED.SetFont(MedProp_11pt);
        HCuOLED.Cursor(0,16);
        HCuOLED.Print(s);
        HCuOLED.Refresh();
}

void drawLong(long uid) {
        HCuOLED.Erase(0,33,120,48);
        HCuOLED.SetFont(MedProp_11pt);
        HCuOLED.Cursor(0,33);
        HCuOLED.Print(uid);
        HCuOLED.Refresh();
}

void drawCardTime(String s) {
  char mess[12];
  s.toCharArray(mess, 12);
  HCuOLED.Erase(0,49,120,80);
  HCuOLED.SetFont(MedProp_11pt);
  HCuOLED.Cursor(0,49);
  HCuOLED.Print(mess);
  HCuOLED.Refresh();
}

Теперь приведу код функции String dump_byte_array(byte *buffer, byte bufferSize). Она вызывается после считывания карты. Работа устройства реализована следующим образом:

1. Переводим последние 4 байта в десятичный формат и выводим на экран uid карты в обоих форматах, ищем совпадение uid'а с тем что мы считали из EEPROM:

Код

    String s;
    unsigned long uiddec = 0;
    unsigned long temp;
    char uid[8];
    for (byte m = (bufferSize > 4 ? (bufferSize - 4) : 0); m < bufferSize; m++) { //берем только последние 4 байта и переводим в десятичную систему
        unsigned long p = 1;
        for(int k = 0; k < bufferSize-m-1; k++) {
          p = p*256;
        }
        uiddec += p*buffer[m];
        s = s + (buffer[m] < 0x10 ? "0" : "");
        s = s + String(buffer[m], HEX);
    }
    s.toCharArray(uid, 8);
    drawUid(uid);
    drawLong(uiddec);
    message = "unknow";
    short currentCard = -1;
    for(int k = 0; k <6; k++) { //сравнимаем полученный uid с тем что хранится в массиве карт
      if((time[k] >=0) && (cards[k] == uiddec)) {
        currentCard = k;
      }
    }

2. Далее идет код в котором реализована логика устройства. В случае если карта известна, но она не мастер, то вызывается функция onRelay(Relay, currentCard), которая проверяет достаточно ли прошло времени для включения.

String onRelay(int relayPin, short card)

  String onRelay(int relayPin, short card) {
  String messa;
  if(time[card] == 0) { //мастер пришел
    offAllCards(); //выключить все карты
    messa = "master ON";
    digitalWrite(relayPin, LOW); //включить реле
  } else {
    if(!isOn[card]) { //если карта не включена
      unsigned long lastOn = (lastTime[card] == NULL ? 0 : lastTime[card]); //последнее включение карты 
      if((millis()-lastOn)/60000 > (freqOn - 1)) { //если последнее включение было раньше, чем разрешенная частота использования
        messa = "time:" + String(time[card]);
        lastTime[card] = someCardsIsOn() + time[card]*60000; //установить время последнего использования в максимальное время из действующих карт + разрешенное время
        isOn[card] = true; //пометить что это одна из действующих карт
        digitalWrite(relayPin, LOW); //включить реле
      } else { //сообщить когда снова можно будет пользоваться
        short waitminutes = freqOn - (millis()-lastOn)/60000; //сколько осталось минут до возможности включить
        messa = "Wait:" + String(waitminutes/60) + "h" + String(waitminutes%60)+ "m";
        //messa = String(waitminutes);
      }
    } else {
      messa = "Allready ON";
    }
  }
  return messa;
}

void offAllCards() {
  for(int k = 0; k < 10; k++) {
    isOn[k] = false;
  }
}
unsigned long someCardsIsOn() { //если какая-то карта уже активна, то время работы устройства увеличивается на время новой карты
  unsigned long maxtime = millis();
  for(int k = 0; k <6; k++) {
    if(isOn[k]&&(lastTime[k] > maxtime)) {
      maxtime = lastTime[k];
    }
  }
  return maxtime;
}

Код

   if(currentCard > 0) { //карта найдена, но она не мастер
      message = onRelay(Relay, currentCard);
    } else {
      if(currentCard == 0) { //приложили мастер, если новая карта приложена ранее, то записать ее в EEPROM, если нет, то сделать выход мастеру
        if(!masterPresent) {
          newCard = false;
          masterPresent = true;
          message = "master in";
          onRelay(Relay, currentCard);
        } else {
          if(newCard) {
            for(int k = 1; k < 6; k++) {
              if(time[k] < 0) {
                writeNewCard(newCardUid,k);
                //Serial.println("new card write to " + String(k));
                newCard = false;
                message = "card added";
                break;
              }
            }
          } else {
            masterPresent=false;
            offRelay(Relay);
            message = "master out";
          }
        }
      } else { //если время меньше 0, то неизвестная карта, если мастер есть, то добавить, если нет то сообщить, что карта неизвестна
        if(masterPresent) {
          newCard = true;
          newCardUid = uiddec;
          message = "confirm?";
        } else {message = "unknow";}
      }
    }
    if(!cardPresent) { //если карт нет, то записать карту как мастер
      short master = 0;
      EEPROM_write(0, uiddec);
      EEPROM_write(4, master);
      message = "NEW MASTER";
      cardPresent = true;
      readCards();
    }
    if(uiddec == 696374757) { //для сброса устройства есть специальная карта
      eraseCards();
      cardPresent = readCards();
      message = "ERASED";
    }
    drawCardTime(message); 
}
void eraseCards() {
  short master = -1;
  for(int t = 0; t < 40; t++) {
  EEPROM_write(t*2, master);}
  readCards();
  //Serial.println("Cards erased");
}
void writeNewCard(unsigned long uid, int k) {
  short time = 30;
  EEPROM_write(k*6, uid);
  EEPROM_write(k*6+4, time);
  readCards();
}

Теперь не забудем отключить устройство когда закончится время, для этого в самое начало функции loop() добавим следующий код:

Код

    if((millis()/10000)%2 ^ i) { //check each 10 seconds
      i = !i;
      if(!masterPresent) {
        if(millis() > (someCardsIsOn()-1000)) {
          offRelay(Relay);
          message = "Time over";
        } else {
          message = "Remain:" + String((someCardsIsOn() - millis())/60000+1);
          Serial.println(message);
        }
        drawCardTime(message);
        HCuOLED.Refresh();
      }
    }

Код скетча целиком:

Код скетча целиком

/*
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             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 <SPI.h>
#include <MFRC522.h>
#include <HCuOLED.h>
#include <EEPROM2.h>

#define RST_PIN		6		// 
#define SS_PIN		7		//

/* Digital pin number for the displays chip select pin */
#define CS_DI 10
/* Digital pin number for the displays data/command pin */
#define DC_DI 9
/* Digital pin number for the displays reset pin */
#define RST_DI 8
#define Relay 3
#define freqOn 300 //как часто можно включать в минутах
unsigned long cards[6]; //uids карт читаются из eeprom
unsigned long newCardUid; //переменная для хранения неизвестной карты
boolean i = false;
short time[6]; //время на которое карта может включить устройство в минутах, читается из eeprom
unsigned long lastTime[6]; //время последнего использования карты
boolean isOn[6]; //активна ли карта в данный момент
boolean cardPresent; //показывает есть ли хоть одна карта в eeprom
boolean masterPresent = false; //показывает присутствие мастера
boolean newCard = false; //показывает присутствие новой карты
String message;

/* Create an instance of the library (uncomment one of the lines below) */
//HCuOLED HCuOLED(SSD1307, SS_DI, DC_DI, RST_DI); // For SSD1307 displays (HCMODU0050 & HCMODU0052)
HCuOLED HCuOLED(SH1106, CS_DI, DC_DI, RST_DI); // For SH1106 displays (HCMODU0058 & HCMODU0059)

MFRC522 mfrc522(SS_PIN, RST_PIN);	// Create MFRC522 instance

void setup() {        
  Serial.begin(9600);		// Initialize serial communications with the PC
  //while (!Serial);		// Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
  pinMode(Relay, OUTPUT);
  digitalWrite(Relay, HIGH);
  SPI.begin();			// Init SPI bus
  mfrc522.PCD_Init();		// Init MFRC522
  ShowReaderDetails();	// Show details of PCD - MFRC522 Card Reader details
  //Serial.println(F("Scan PICC to see UID, type, and data blocks..."));
  HCuOLED.Reset();
  //HCuOLED.SetFont(MedProp_11pt);
  //HCuOLED.Cursor(40,0);
  //HCuOLED.Print("Uid:");
  //eraseCards();
  cardPresent = readCards();
}

void loop() {
    if((millis()/10000)%2 ^ i) { //check each 10 seconds
      i = !i;
      if(!masterPresent) {
        if(millis() > (someCardsIsOn()-1000)) {
          offRelay(Relay);
          message = "Time over";
        } else {
          message = "Remain:" + String((someCardsIsOn() - millis())/60000+1);
          Serial.println(message);
        }
        drawCardTime(message);
        HCuOLED.Refresh();
      }
    }
	// Look for new cards
	if ( ! mfrc522.PICC_IsNewCardPresent()) {
		return;
	}

	// Select one of the cards
	if ( ! mfrc522.PICC_ReadCardSerial()) {
		return;
	}

        String s = dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
        mfrc522.PICC_HaltA();
        Serial.println(s);
}

void ShowReaderDetails() {
	// Get the MFRC522 software version
	byte v = mfrc522.PCD_ReadRegister(mfrc522.VersionReg);
	Serial.print(F("MFRC522 Software Version: 0x"));
	Serial.print(v, HEX);
	if (v == 0x91)
		Serial.print(F(" = v1.0"));
	else if (v == 0x92)
		Serial.print(F(" = v2.0"));
	else
		Serial.print(F(" (unknown)"));
	Serial.println("");
	// When 0x00 or 0xFF is returned, communication probably failed
	if ((v == 0x00) || (v == 0xFF)) {
		Serial.println(F("WARNING: Communication failure, is the MFRC522 properly connected?"));
	}
}

String dump_byte_array(byte *buffer, byte bufferSize) {
    String s;
    unsigned long uiddec = 0;
    unsigned long temp;
    char uid[8];
    for (byte m = (bufferSize > 4 ? (bufferSize - 4) : 0); m < bufferSize; m++) { //берем только последние 4 байта и переводим в десятичную систему
        unsigned long p = 1;
        for(int k = 0; k < bufferSize-m-1; k++) {
          p = p*256;
        }
        uiddec += p*buffer[m];
        s = s + (buffer[m] < 0x10 ? "0" : "");
        s = s + String(buffer[m], HEX);
    }
    s.toCharArray(uid, 8);
    drawUid(uid);
    drawLong(uiddec);
    message = "unknow";
    short currentCard = -1;
    for(int k = 0; k <6; k++) { //сравнимаем полученный uid с тем что хранится в массиве карт
      if((time[k] >=0) && (cards[k] == uiddec)) {
        currentCard = k;
      }
    }
    if(currentCard > 0) { //карта найдена, но она не мастер
      message = onRelay(Relay, currentCard);
    } else {
      if(currentCard == 0) { //приложили мастер, если новая карта приложена ранее, то записать ее в EEPROM, если нет, то сделать выход мастеру
        if(!masterPresent) {
          newCard = false;
          masterPresent = true;
          message = "master in";
          onRelay(Relay, currentCard);
        } else {
          if(newCard) {
            for(int k = 1; k < 6; k++) {
              if(time[k] < 0) {
                writeNewCard(newCardUid,k);
                //Serial.println("new card write to " + String(k));
                newCard = false;
                message = "card added";
                break;
              }
            }
          } else {
            masterPresent=false;
            offRelay(Relay);
            message = "master out";
          }
        }
      } else { //если время меньше 0, то неизвестная карта, если мастер есть, то добавить, если нет то сообщить, что карта неизвестна
        if(masterPresent) {
          newCard = true;
          newCardUid = uiddec;
          message = "confirm?";
        } else {message = "unknow";}
      }
    }
    if(!cardPresent) { //если карт нет, то записать карту как мастер
      short master = 0;
      EEPROM_write(0, uiddec);
      EEPROM_write(4, master);
      message = "NEW MASTER";
      cardPresent = true;
      readCards();
    }
    if(uiddec == 696374757) {
      eraseCards();
      cardPresent = readCards();
      message = "ERASED";
    }
    drawCardTime(message); 
    return s;
}

void drawUid(char *s) {
        HCuOLED.Erase(0,16,120,32);
        HCuOLED.SetFont(MedProp_11pt);
        HCuOLED.Cursor(0,16);
        HCuOLED.Print(s);
        HCuOLED.Refresh();
}

void drawLong(long uid) {
        HCuOLED.Erase(0,33,120,48);
        HCuOLED.SetFont(MedProp_11pt);
        HCuOLED.Cursor(0,33);
        HCuOLED.Print(uid);
        HCuOLED.Refresh();
}

void drawCardTime(String s) {
  char mess[12];
  s.toCharArray(mess, 12);
  HCuOLED.Erase(0,49,120,80);
  HCuOLED.SetFont(MedProp_11pt);
  HCuOLED.Cursor(0,49);
  HCuOLED.Print(mess);
  HCuOLED.Refresh();
}

void eraseCards() {
  short master = -1;
  for(int t = 0; t < 40; t++) {
  EEPROM_write(t*2, master);}
  readCards();
  //Serial.println("Cards erased");
}

boolean readCards() {
  cardPresent = false;
  for(int k = 0; k <6; k++) {
    EEPROM_read(k*6+4, time[k]);
    if(time[k] >= 0) {
      cardPresent = true;
      EEPROM_read(k*6, cards[k]);
      //Serial.print(cards[k]);
      //Serial.print(" - ");
      //Serial.println(time[k]);
      }
  }
  return cardPresent;
}

void writeNewCard(unsigned long uid, int k) {
  short time = 30;
  EEPROM_write(k*6, uid);
  EEPROM_write(k*6+4, time);
  readCards();
}

String onRelay(int relayPin, short card) {
  String messa;
  if(time[card] == 0) { //мастер пришел
    offAllCards(); //выключить все карты
    messa = "master ON";
    digitalWrite(relayPin, LOW); //включить реле
  } else {
    if(!isOn[card]) { //если карта не включена
      unsigned long lastOn = (lastTime[card] == NULL ? 0 : lastTime[card]); //последнее включение карты 
      if((millis()-lastOn)/60000 > (freqOn - 1)) { //если последнее включение было раньше, чем разрешенная частота использования
        messa = "time:" + String(time[card]);
        lastTime[card] = someCardsIsOn() + time[card]*60000; //установить время последнего использования в максимальное время из действующих карт + разрешенное время
        isOn[card] = true; //пометить что это одна из действующих карт
        digitalWrite(relayPin, LOW); //включить реле
      } else { //сообщить когда снова можно будет пользоваться
        short waitminutes = freqOn - (millis()-lastOn)/60000; //сколько осталось минут до возможности включить
        messa = "Wait:" + String(waitminutes/60) + "h" + String(waitminutes%60)+ "m";
        //messa = String(waitminutes);
      }
    } else {
      messa = "Allready ON";
    }
  }
  return messa;
}

void offRelay(int relayPin) {
  offAllCards();
  digitalWrite(relayPin, HIGH);
}

void offAllCards() {
  for(int k = 0; k < 10; k++) {
    isOn[k] = false;
  }
}

unsigned long someCardsIsOn() {
  unsigned long maxtime = millis();
  for(int k = 0; k <6; k++) {
    if(isOn[k]&&(lastTime[k] > maxtime)) {
      maxtime = lastTime[k];
    }
  }
  return maxtime;
}

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

image
Arduino: ограничение на время просмотра телевизора с помощью RFID RC522 и - 7

Ссылка на скетч

Демонстрация работы:

Что хотелось бы изменить/добавить:

1. Сделать возможность на изменение времени использования устройства для карты и ее удаление (прикладывая мастер несколько раз изменять время: 30, 45, 60, 90, 120, -1).
2. Добавить на схему кнопку с помощью которой можно было бы сбрасывать устройство.

Автор: tmv002

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js