Bezprzewodowy rejestrator czasu pracy


Tomasz Bartuś



2020-01-30
Bezprzewodowy rejestrator czasu pracy

Wstęp

Przy zatrudnianiu osób "na godziny" często zachodzi problem kontroli czasu pracy. Dawniej po prostu robiło się notatki czasów przyjścia do pracy i jej zakończenia. Taki sposób rozliczania pracowników posiada jednak podstawową wadę. Ktoś na jakiejś podstawie musi te wpisy weryfikować. Chciałbym w tym odcinku przedstawić elektroniczny system kontroli czasu pracy, który jest pozbawiony tego mankamentu. W warunkach domowych można go wykorzystać do rozliczeń płatności np. za opiekę nad dzieckiem czy osobą w podeszłym wieku. Przed zaprojektowaniem systemu postawiono kilka założeń wstępnych:

  1. układ powinien posiadać autonomiczne zasilanie,
  2. powinien rejestrować daty i czasy rozpoczęcia oraz zakończenia pracy,
  3. powinien umożliwiać rejestrację tych zdarzeń w celu dalszej obróbki w arkuszach kalkulacyjnych,
  4. powinien umożliwiać prostą i wygodną obsługę przez dowolną liczbę pracowników,
  5. układ rejestrujący czasy przyjścia i zakończenia pracy powinien być galwanicznie odseparowany od modułu rejestrującego te zdarzenia,
  6. powinien umożliwiac pracę bez przesyłania danych za pośrednictwem internetu bądź sieci komórkowych.

Realizację takiego układu umożliwia wykorzystanie modułów: zegara czasu rzeczywistego, radiowego przesyłania danych oraz rejestratora RFID z kartami elektronicznymi i brelokami.

Bezprzewodowy rejestrator czasu pracy
Fig. 1. Bezprzewodowy rejestrator czasu pracy

Rejestrator czasu pracy jaki wykonamy będzie składał się z dwóch autonomicznych układów: logera-nadajnika oraz odbiornika-rejestratora (Fig. 1). Oba układy będą oparte na mikrokontrolerach Arduino (Nano oraz Uno) i będą zasilane przez ogniwa litowo-jonowe 18650. W związku z tym, że ich napięcie wynosi około 4,2V, do zasilania obu układów zastosujemy dwie identyczne przetwornice impulsowe step-up podwyższające napięcie do 5V (Fig. 2). Oba układy będą także zaopatrzone w moduły ładowania ogniw Li-jon 18650 TP4056 (Fig. 3).

Moduł przetwornicy impulsowej 2-5V Step-up 5V 2A
Fig. 2. Moduł przetwornicy impulsowej 2-5V Step-up 5V 2A
Moduł ładowania ogniw Li-jon 18650 TP4056
Fig. 3. Moduł ładowania ogniw Li-jon 18650 TP4056

1. Czytnik RFID z nadajnikiem

Zadaniem układu loggera z nadajnikiem będzie rejestrowanie migracji osób. Każda osoba wchodząc lub wychodząc z pomieszczenia będzie logowała się do systemu za pomocą jednego urządzenia RFID. Rejestrator odnotuje czas oraz status (wejście/wyjście) każdej z nich. Realizacja takiego zadania wymaga uwzględnienia możliwych awarii systemu np. spowodawanych zanikiem zasilania. Załóżmy, że osoba wchodząca rejestruje swoje wejście za pomocą przyłożenia karty zbliżeniowej PICC. System w odpowiednich zmiennych zapamięta czas i status tego zdarzenia. Jeśli przed wyjściem tej samej osoby z pomieszczenia nastąpi awaria, zmienne zostaną zresetowane i system przy wyjściu tej osoby zarejestruje niepoprawny status wejścia. Widać więc, że obok rejestracji UID osoby istnieje konieczność rozróżnienia konkretnego stanu wejścia lub wyjścia. Można to zrealizować na dwa sposoby. Pierwszym z nich jest rejestracja aktualnego statusu każdej z osób w pamięci EEPROM mikrokontrolera. Przy każdym zdarzeniu, dla każdego UID, układ może trwale zapisywać statusy w układzie scalonym. Istnieją dwie słabe strony takiego rozwiązania. Po pierwsze liczba możliwych cykli zapisu / odczytu do i z pamięci EEPROM nie jest nieograniczona. Według danych producentów możliwe jest wykonanie około 100000 takich cykli. Druga wada tego rozwiązania, podobnie jak poprzednio, jest związana z możliwością rejestracji nieprawdziwego statusu osób. Jeśli pomiędzy dwoma zdarzeniami nastąpiła awaria systemu, do pamięci EEPROM może być zapisywany niepoprawny status kolejnego zdarzenia. W realizowanym układzie proponuję inne rozwiązanie. Układ zostanie wyposażony w dwa przyciski. Za pomocą, jednego z nich każda rejestrowana osoba będzie samodzielnie deklarowała status aktualnego zdarzenia. Drugi przycisk wykorzystamy do zatwierdzenia wprowadzanych danych.

Części

  1. platforma Arduino Nano,
  2. koszyczek na ogniwo 1 × 18650,
  3. moduł przetwornicy impulsowej 2-5V Step-up 5V 2A (Fig. 2),
  4. moduł ładowania ogniw Li-jon 18650 TP4056 (Fig. 3),
  5. moduł rejestratora RFID RC522 z kartami zbliżeniowymi i brelokami (Fig. 4),
  6. wyświetlacz znakowy LCD 20 × 4,
  7. moduł zegara czasu rzeczywistego DS3231 (Fig. 5),
  8. moduły komunikacji radiowej FS100A (Fig. 6),
  9. buzzer z generatorem 5V, 12mm,
  10. rezystor 100Ω,
  11. mikrowłączniki (2 szt.),
  12. płytka prototypowa,
  13. przewody/mostki.
Moduł rejestratora RFID RC522 z kartą zbliżeniową i brelokiem
Fig. 4. Moduł rejestratora RFID RC522 z kartą zbliżeniową i brelokiem
Moduł zegara czasu rzeczywistego DS3231
Fig. 5. Moduł zegara czasu rzeczywistego DS3231

Moduły komunikacji radiowej FS100A
Fig. 6. Moduły komunikacji radiowej FS100A

Projekt układu czytnika kart zbliżeniowej PICC z nadajnikiem oprzemy na projekcie rejestratora RFID RC522. Modyfikacje układu będą obejmowały: zmianę wyświetlacza LCD z 16 × 2 na 20 × 4, dodanie modułu zegara czasu rzeczywistego (Fig. 5), dodanie buzzera oraz dodanie dwóch przycisków sterujących. Zmiana modułu wyświetlacza umożliwi stałe wyświetlanie czasu oraz daty. Moduł zegara czasu rzeczywisytego, podobnie jak wyświetlacz LCD, wykorzystuje magistralę I2C. Sygnały SDA oraz SCL podpinamy więc odpowiednio do pinów A4 i A5 układu arduino. Buzzer poprzez rezystor 100Ω ograniczający prąd podpinamy do pinu cyfrowego D4. Przyciski podpinamy bezpośrednio do pinów cyfrowych D2 i D3 mikrokontrolera. Zastosujemy podciąganie programowe pullup. Po zarejestrowaniu zdarzenia układ będzie miał zadanie bezprzewodowo przesłać niezbędne informacje (czas zdarzenia, identyfikator osoby oraz status zdarzenia) do układu odbiornika z rejestratorem. Wykorzystamy do tego nadajnik komunikacji radiowej FS100A (Fig. 6).

Schemat

Schemat montażowy czytnika RFID z nadajnikiem
Fig. 7. Schemat montażowy czytnika RFID z nadajnikiem

Kod

#include <SPI.h>                            // Attach SPI interface library
#include <MFRC522.h>                        // Attach RC522 module library
#include <Wire.h>                           // Attach I2C interface library
#include <LiquidCrystal_I2C.h>              // Attach LCD I2C library
#include <VirtualWire.h>                    // Attach RF modules library

#define DS3231_I2C_ADDRESS 0x68             // Set up real time clock
#define EVENT_PIN 2                         // Define no of the EVENT button pin
#define OK_PIN 3                            // Define no of the OK button pin
#define BUZZER_PIN 4                        // Define no of the buzzer pin
#define LED_PIN 5                           // Define no of the LED pin
#define TRANSMIT_PIN 6                      // Define FS100A data transmit pin
#define RST_PIN 9                           // Define connection of the RST_PIN of the RFID module
#define SS_PIN 10                           // Define connection of the SS_PIN of the RFID module

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set up LCD
MFRC522 mfrc522(SS_PIN, RST_PIN);           // Set up mfrc522 (RFID)

boolean event = 0;                          // Event selection variable - 0 / 1
boolean sending = 0;                        // Sending selection variable - 0 / 1
String eventName = "input";                 // Default event name variable - input
unsigned long timelight;                    // timelight variable to switch off the backlight of the LCD
int screen = 0;

int count_2 = 0;
unsigned long time_screen_2 = 0;
unsigned long screen_2_start = 0;

byte bcdToDec(byte val) {                   // Konwersja liczby binarnej do postaci dziesiętnej
  return ( (val / 16 * 10) + (val % 16) );
}

void setup() {
  pinMode(EVENT_PIN, INPUT_PULLUP);         // Event selector button
  pinMode(OK_PIN, INPUT_PULLUP);            // OK button
  pinMode(BUZZER_PIN, OUTPUT);              // Buzzer pin declaration
  pinMode(LED_PIN, OUTPUT);                 // LED pin declaration

  Wire.begin();                             // Initiate the wire library and join the I2C bus as a master or slave

  vw_set_tx_pin(TRANSMIT_PIN);              // Set up virtual vire (FS100A) TX pin
  vw_setup(2000);                           // Set up virtual vire transmission speed
  
  digitalWrite(LED_PIN, HIGH);              // Turn off the LED
  
  Serial.begin(9600);

  lcd.begin(20, 4);                         // Initialization the interface to the LCD screen and specify the dimensions (width and height)
  lcd.backlight();                          // Switch LCD backlight on
  lcd.setCursor(3, 0);                      // Set cursor position to the 3 collumn and 0 row
  lcd.print("RFID controler");              // Write the text on LCD
  lcd.setCursor(10, 1);                     // Set cursor position to the 10 collumn and 1 row
  lcd.print("&");                           // Write the text on LCD
  lcd.setCursor(5, 2);                      // Set cursor position to the 5 collumn and 2 row
  lcd.print("transsmiter");                 // Write the text on LCD
  lcd.setCursor(1, 3);                      // Set cursor position to the 1 collumn and 3 row
  lcd.print("Tomasz Bartus'2020");          // Write the text on LCD
  delay(500);

  SPI.begin();                              // Initialization of the SPI interface
  mfrc522.PCD_Init();                       // Initialize Proximity Coupling Device
  delay(2000);
  lcd.clear();                              // Clear the screen
  timelight = millis();
  screen = 1;
}

void readDS3231time(byte *second,
                    byte *minute,
                    byte *hour,
                    byte *dayOfWeek,
                    byte *dayOfMonth,
                    byte *month,
                    byte *year)
{
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0);                                  // ustawia rejestr DS3231 na 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);        // żąda 7 bajtów danych od modułu DS3231 począwszy od rejestru 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}

void displayTime() {
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;

  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);        // retrieve data from DS3231

  // Przekazanie danych do wyświetlenie na LCD
  // Time
  // podczas wyświetlania konwertuje wartość zmiennej typu bitowego do postaci dziesiętnej
  if (hour < 10) {                            // Hours
    lcd.setCursor(0, 3);                      // Set cursor position to the 0 collumn and 3 row
    lcd.print("0");
    lcd.print(hour, DEC);
  } else {
    lcd.setCursor(0, 3);                      // Set cursor position to the 0 collumn and 3 row
    lcd.print(hour, DEC);
  }
  // lcd.setCursor(2,1);
  lcd.print(":");

  if (minute < 10) {                          // Minutes
    lcd.setCursor(3, 3);                      // Set cursor position to the 3 collumn and 3 row
    lcd.print("0");
    lcd.print(minute, DEC);
  } else {
    lcd.setCursor(3, 3);                      // Set cursor position to the 3 collumn and 3 row
    lcd.print(minute, DEC);
  }
  lcd.print(":");

  if (second < 10) {                          // Seconds
    lcd.setCursor(6, 3);                      // Set cursor position to the 6 collumn and 3 row
    lcd.print("0");
    lcd.print(second, DEC);
  } else {
    lcd.setCursor(6, 3);                      // Set cursor position to the 6 collumn and 3 row
    lcd.print(second, DEC);
  }

  lcd.setCursor(4, 2);                        // Set cursor position to the 4 collumn and 2 row
  lcd.print("0");                               // DATA

  if (dayOfMonth < 10) {                        // Day
    lcd.setCursor(4, 2);                      // Set cursor position to the 4 collumn and 2 row
    lcd.print("0");
    lcd.print(dayOfMonth, DEC);
  } else {
    lcd.setCursor(4, 2);                      // Set cursor position to the 4 collumn and 2 row
    lcd.print(dayOfMonth, DEC);
  }
  lcd.print(".");

  if (month < 10) {                             // Mounth
    lcd.setCursor(7, 2);                        // Set cursor position to the 7 collumn and 2 row
    lcd.print("0");
    lcd.print(month, DEC);
  } else {
    lcd.setCursor(7, 2);                        // Set cursor position to the 7 collumn and 2 row
    lcd.print(month, DEC);
  }
  lcd.print(".");

  lcd.print("20");                              // Year
  if (year < 10) {
    lcd.setCursor(12, 2);                        // Set cursor position to the 12 collumn and 2 row
    lcd.print("0");
    lcd.print(year, DEC);
  } else {
    lcd.setCursor(12, 2);                        // Set cursor position to the 12 collumn and 2 row
    lcd.print(year, DEC);
  }

  lcd.setCursor(0, 2);                        // Set cursor position to the 0 collumn and 2 row
  switch (dayOfWeek) {                          // Day of the week
    case 1:
      lcd.print("Sun");
      break;
    case 2:
      lcd.print("Mon");
      break;
    case 3:
      lcd.print("Tue");
      break;
    case 4:
      lcd.print("Wed");
      break;
    case 5:
      lcd.print("Thu");
      break;
    case 6:
      lcd.print("Fri");
      break;
    case 7:
      lcd.print("Sat");
      break;
  }
}

void loop() {

String toSend = ("Tomasz Bartus");        // Message text
  char msg[20];                             // Create 20-elements char table
  toSend.toCharArray(msg, toSend.length() + 1); // convert text to char table

  
  if (millis() - timelight <= 10000) {          // If 10 sec have not passed,
    lcd.backlight();                            // switch LCD backlight on
  } else {
    lcd.noBacklight();                          // switch LCD backlight off

    if ( (mfrc522.PICC_ReadCardSerial() == 1) || (digitalRead(2) == LOW) || (digitalRead(3) == LOW)) {    // True, if RFID tag/card is present
      timelight = millis();                     // switch LCD backlight on for 10 sec
    }
  }

  switch (screen) {
    case 1:                                   // SCREEN 1
      lcd.clear();                            // Clear the screen
      lcd.setCursor(0, 0);                    // Set cursor position to the 0 collumn and 0 row
      lcd.print("Press your ID card");        // Write the text on LCD
      lcd.setCursor(0, 1);                    // Set cursor position to the 0 collumn and 1 row
      lcd.print("please...");                 // Write the text on LCD
      displayTime();                          // wyświetlanie czasu rzeczywistego
      delay(1000);                            // Refresh every second
      if ( mfrc522.PICC_IsNewCardPresent()) { // True, if RFID tag/card is present
        screen = 2;                           // Go to screen 2
        lcd.clear();                          // Clear the screen
      }
      break;

    case 2:                                   // SCREEN 2

      lcd.setCursor(0, 0);                    // Set cursor position to the 0 collumn and 0 row
      lcd.print("UID: ");                     // Write the text on LCD
      if (digitalRead(2) == LOW) {            // If an event selector button has been pressed
        delay(20);                            // Wait 20ms to stabilise button debounce
        event = !event;                       // Change event variable to opposed
        if (event == 0) {                     // If an event variable is equal 0
          eventName = "input ";               // Set an eventName variable to "input"
          tone(4, 500);
          delay(200);
          tone(4, 1000);
          delay(200);
          tone(4, 1500);
          delay(200);
          noTone(4);
        } else {                              // If event variable is equal 1
          eventName = "output";               // Set an eventName variable to "output"
          tone(4, 1500);
          delay(200);
          tone(4, 1000);
          delay(200);
          tone(4, 500);
          delay(200);
          noTone(4);
        }
        while (digitalRead(2) == LOW) {
          delay(20);
        }
      }

      lcd.setCursor(14, 1);                       // Set cursor position to the 14 collumn and 1 row
      lcd.print(eventName);                       // Write the eventName variable on LCD
      lcd.setCursor(4, 0);                        // Set cursor position to the 4 collumn and 0 row
      if ( mfrc522.PICC_IsNewCardPresent()) {     // True, if RFID tag/card is present
        //        lcd.backlight();                          // Turning on LCD backlight
        if ( mfrc522.PICC_ReadCardSerial()) {     // True, if RFID tag/card was read
          for (byte i = 0; i < mfrc522.uid.size; i++) {             // read id (in parts)
            lcd.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
            lcd.print(mfrc522.uid.uidByte[i], DEC);                 // print uid as dec values
          }
        }
      }
      lcd.setCursor(0, 1);                  // Set cursor position to the 0 collumn and 1 row
      lcd.print("Select event: ");          // Write the text on LCD
      lcd.setCursor(0, 2);                  // Set cursor position to the 0 collumn and 2 row
      lcd.print("Confirmation: none");      // Write the text on LCD

      if (digitalRead(OK_PIN) == LOW) {    // If an OK button has been pressed
        delay(20);                         // Wait 20ms to stabilise button debounce
        digitalWrite(LED_PIN, LOW);        // Turn on the LED
        lcd.setCursor(14, 2);              // Set cursor position to the 15 collumn and 1 row


        String toSend = ("hello world");              // tekst wiadomości
        char msg[23]; // tworzymy tablicę typu char
        toSend.toCharArray(msg, toSend.length() + 1); // konwertujemy nasz tekst do tablicy char'ów
        vw_send((uint8_t *)msg, strlen(msg));         // wysyłamy
        vw_wait_tx();



        lcd.print("send");                 // Write the eventName variable on LCD
        tone(4, 400);
        delay(1000);
        noTone(4);
        screen = 3;                        // Go to screen 2
        lcd.clear();                       // Clear the screen
        while (digitalRead(OK_PIN) == LOW) {
          delay(20);
        }
      }
//=================                       // If there is no confirmation - come back to screen 1
      count_2++;                              
      if (count_2 == 1) {
        screen_2_start = millis();        // Measure time of the screen 2 appearance
      }
      time_screen_2 = millis() - screen_2_start;  // Count period of time when the screen 2 is visible
      if (time_screen_2 >= 15000) {           // If the screen 2 is visible longer than 15 sec
        screen = 1;                           // Come back to screen 1
        count_2 = 0;                          // Reset the count_2 variable
      }
//=================
      break;

    case 3:                                   // SCREEN 3
      lcd.setCursor(0, 0);                    // Set cursor position to the 0 collumn and 0 row
      lcd.print("The ");                      // Write the text on LCD
      lcd.print(eventName);                   // Write the eventName variable on LCD
      lcd.print(" event");                    // Write the text on LCD
      lcd.setCursor(0, 1);                    // Set cursor position to the 0 collumn and 1 row
      lcd.print("has been registed.");        // Write the text on LCD
      lcd.setCursor(11, 3);                   // Set cursor position to the 11 collumn and 3 row
      lcd.print("Thanks...");                 // Write the text on LCD
      delay(2000);
      screen = 1;                             // Come back to screen 1
      break;
  }
  digitalWrite(LED_PIN, HIGH);                // Turn off the LED
  Serial.print(screen);
  Serial.println("; ");
}

2. Odbiornik z rejestratorem

Odbiornik-rejestrator składa się z jednego urządzenia wejściowego - modułu odbiornika RFID RC522 (Fig. 5) . oraz jednego urządzenia wyjściowego - czytnika kard SD (Fig. 8) .

Części

  1. platforma Arduino Nano,
  2. koszyczek na ogniwo 1 × 18650,
  3. moduł przetwornicy impulsowej 2-5V Step-up 5V 2A (Fig. 2),
  4. moduł ładowania ogniw Li-jon 18650 TP4056 (Fig. 3),
  5. moduły komunikacji radiowej FS100A (Fig. 6),
  6. moduł czytnika kard SD (Fig. 8),
  7. płytka prototypowa,
  8. przewody/mostki.

Moduł czytnika kart SD
Fig. 8. Moduł czytnika kart SD

Schemat

Schemat montażowy układu odbiornika z rejestratorem
Fig. 9. Schemat montażowy układu odbiornika z rejestratorem

Działanie

Układ czytnik RFID z nadajnikiem cały czas nasłuchuje pojawienia się karty zbliżeniowej lub breloka zbliżeniowego. W tym czasie na wyświetlaczu LCD wyświetlany jest monit oczekiwania wraz z aktualną datą i godziną (Fig. 10; zob. kod case 1).

Ekran pierwszy
Fig. 10. Ekran oczekiwania na pojawienie się urządzenia RFID

Po zbliżeniu urządzenia RFID do rejestratora RC522 rejestrowany jest dokładny czas zdarzenia. Na wyświetlaczu pojawia się komunikat o statusie zdarzenia (Event: input) oraz komunikat o statusie rejestracji zdarzenia (Confirmation: none) (Fig. 11; zob. kod case 2).

Ekran drugi
Fig. 11. Ekran wyświetlany w momencie wykrycia urządzenia RFID

Program przez 15 sekund oczekuje na reakcję użytkownika. Należy wtedy wybrać za pomocą przycisku "Input / Output" odpowiedni rodzaj zdarzenia, t.j. wejście (input) lub wyjście (output) oraz potwierdzić zapis zdarzenia klawiszem "Zarejestruj". W zależności od tego, które zdarzenie zostaje wybrane (wejście czy wyjście), generowany jest odpowiedni charakterystyczny sygnał dźwiękowy. Gdy zostanie naciśnięty przycisk "Zarejestruj", do transmitera przekazywany jest komunikat w formacie użytkownik#czas_zdarzebnia#status_zdarzenia. Na wyświetlaczu w tym czasie pojawia się komunikat "Confirmation: send" (Fig. 12; zob. kod case 2).

Ekran drugi w trakcie wysyłania komunikatu do odbiornika-rejestratora
Fig. 12. Ekran potwierdzający wysłanie raportu do urządzenia odbiornika-rejestratora

Po wysłaniu komunikatu do rządzenia odbiornika-rejestratora na wyświetlaczu LCD czytnika-nadajnika pojawia się komunikat potwierdzający rejestrację zdarzenia (Fig. 13; zob. kod case 3).

Ekran trzeci
Fig. 13. Ekran potwierdzający rejestrację zdarzenia

Jeżeli odbiornik odbierze przesłany tekst, po skonwertowaniu go z kodu szesnastkowego na znaki ASCII zapisuje wiersz komunikatu na karcie SD. Komunikaty zgromadzone na karcie można przetwarzać w arkuszach kalkulacyjnych w celu generowania raportów obliczających łączne czasy pracy.

Film




Wykorzystane materiały

Elektroprzewodnik, 2014. Przyciski | #25 [Arduino].
Elektro Maras, 2014. Jak przesyłać dane z użyciem nadajnika i odbiornika RF?
ELECTRONOOBS, 2018. Arduino RFID door lock
Gałaś P., 2015. Kurs Arduino #10: Obsługa modułów RF 433MHz i 315MHz
saiyojeff, 2014. How to pass a function from the RFID-RC522 into a variable then be sent into either serial or ethernet?
 
 

Doktorat

Spis treści
Rozdzialy
Abstrakt [pl]
Abstract [eng]