Making a Homemade RFID Punch Clock

by jjosemayorga in Circuits > Arduino

151 Views, 0 Favorites, 0 Comments

Making a Homemade RFID Punch Clock

Miniatura.png

In this Instructable I'll show you how to build a homemade punch clock working with RFID cards.

This is a non-rigurous English translation for an article in my personal website: https://jjosemayorga.com/creando-una-maquina-de-fichar/.


You can find this and many other interesting projects in my website and my social media.

🌐 https://jjosemayorga.com/

Twitter ► https://twitter.com/jjosemayorga

Instagram ► https://www.instagram.com/jjosemayorga/

Facebook ► https://www.facebook.com/jjosemayorgau

Youtube ► https://www.youtube.com/@jjosemayorga

TikTok ► https://www.tiktok.com/@jjosemayorga

Supplies

Wire the Components All Together

Esquema RFID_bb.png

In order to make the whole setup work, you just need to wire all the components in the way I show you in this picture.

If you need further explanation on how each element works or why I use them that way, you just need to visit the article on my website and get some instruction on it (it's in Spanish but, if you are not fluent in that language, Google translator will do the job 😉).

Load the Arduino Code

As for the Arduino part, I provide you the Arduino code I've used.

It's a pretty simple of the use of serial communication. Should you need to learn the very basics of this, let me address you again to the article on my website.

Downloads

Build the Java App

Here you will find the Java code.

package fichajes;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
import java.text.*;
import java.awt.event.*;
import java.awt.*;
import javafx.embed.swing.JFXPanel;
import javafx.stage.FileChooser;
import javax.swing.*;
import java.io.*;
import javax.imageio.*;
import com.sun.glass.ui.Platform;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import java.io.IOException;
public class MaquinaFichar extends JFrame implements SerialPortEventListener
{
int segundos;//manejar el valor del contador
boolean frozen; //manejar el estado del contador
Timer timer = new Timer();
Date date = new Date();
SimpleDateFormat ft=new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");

JFrame marco=new JFrame("Aplicación de control de fichajes");
JLabel titulo=new JLabel();
JLabel hora=new JLabel();
JLabel onoff=new JLabel();
JButton encender=new JButton();
JLabel inicioregistro=new JLabel("Inicio de registro mostrado");
JTextField tf_inicioregistro=new JTextField(20);
JLabel finregistro=new JLabel("Fin de registro mostrado");
JTextField tf_finregistro=new JTextField(20);
JButton btn_guardar=new JButton();
JButton btn_abrir=new JButton();
int encendido=0;
int conectado=1;
JPanel pnl_encendido=new JPanel();
JLabel lbl_puerto=new JLabel("Puerto de conexión:");
String[] str_puerto={"COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9","COM10","COM11","COM12","COM13","COM14","COM15","COM16","COM17","COM18","COM19","COM20","COM21","COM22","COM23","COM24","COM25","COM26"};
JComboBox cmb_puerto=new JComboBox(str_puerto);
JLabel lbl_baudrate=new JLabel("Baud Rate:");
String[] str_baudrate={"4800","9600","19200","38400","57600","115200","234000","460800","921600","1382400"};
JComboBox cmb_baudrate=new JComboBox(str_baudrate);
JTextField txt_noticias=new JTextField(40);
JPanel pnl_puerto=new JPanel();
JPanel pnl_baudrate=new JPanel();
final JFXPanel fxPanel = new JFXPanel();

File selectedFile=null;
File savedFile=null;
String nombrearchivo=null;
String infile=null;
JTextArea texto=new JTextArea();
JScrollPane scroll=new JScrollPane(texto);
String personal[][]=new String[2][2];


/** The output stream to the port */
private OutputStream output = null;
private BufferedReader input;
SerialPort serialPort;
private String PORT_NAME = "COM1";
/** Milliseconds to block while waiting for port open */
private static final int TIME_OUT = 2000;
/** Default bits per second for COM port. */
private static int DATA_RATE = 9600;

MaquinaFichar()
{
try
{
marco.setContentPane(new JLabel(new ImageIcon(ImageIO.read(new File("fondo.png")))));
}
catch(IOException e)
{
e.printStackTrace();

}
personal[0][0]="25 9A 0F 11";
personal[1][0]="81 EE 3C D9";
personal[0][1]="out";
personal[1][1]="out";
timer.schedule(new MiTarea(), 0, 1000);
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setIconImage(Toolkit.getDefaultToolkit().createImage("maquinafichar.png"));
marco.setResizable(false);
GridBagLayout gridbag=new GridBagLayout();
GridBagConstraints gbc=new GridBagConstraints();
marco.setLayout(gridbag);
gbc.insets=new Insets(10,10,10,10);
gbc.gridx=0;
gbc.gridy=0;
gbc.gridwidth=6;
gbc.gridheight=1;
gbc.weightx=1;
gbc.weighty=1;
titulo.setFont(new Font("Arial", Font.PLAIN, 60));
titulo.setText("REGISTRO DE FICHAJES");
marco.add(titulo,gbc);
gbc.gridx=0;
gbc.gridy=1;
gbc.gridwidth=4;
gbc.gridheight=1;
gbc.weightx=1;
gbc.weighty=1;
hora.setFont(new Font("Arial", Font.PLAIN, 48));
hora.setText(ft.format(date));
hora.setOpaque(true);
hora.setBackground(Color.cyan);
marco.add(hora,gbc);
/*gbc.gridx=4;
gbc.gridy=1;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.weightx=1;
gbc.weighty=1;
gbc.anchor=GridBagConstraints.EAST;*/
//encender.setPreferredSize(new Dimension(100, 100));
encender.setIcon(new ImageIcon("redbutton.png"));
encender.setName("encender");
encender.setToolTipText("Encender/Apagar");
encender.setBorderPainted(false);
encender.setContentAreaFilled(false);
encender.setFocusPainted(false);
encender.setCursor(new Cursor(Cursor.HAND_CURSOR));
//encender.setOpaque(false);
encender.addMouseListener(new Seleccion());
pnl_encendido.add(encender,gbc);
/*gbc.gridx=5;
gbc.gridy=1;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.weightx=1;
gbc.weighty=1;
gbc.anchor=GridBagConstraints.WEST;*/
//onoff.setText("<html><font size=20 color=red>OFF</font></html>");
//encender.setPreferredSize(new Dimension(200, 100));
onoff.setText("OFF");
onoff.setFont(new Font("Arial", Font.PLAIN, 100));
onoff.setForeground(Color.red);
pnl_encendido.add(onoff,gbc);
gbc.gridx=4;
gbc.gridy=1;
gbc.gridwidth=2;
gbc.gridheight=1;
gbc.weightx=1;
gbc.weighty=1;
gbc.anchor=GridBagConstraints.WEST;
pnl_encendido.setOpaque(false);
marco.add(pnl_encendido,gbc);
gbc.gridx=0;
gbc.gridy=2;
gbc.gridwidth=4;
gbc.gridheight=5;

texto.setEditable(false);
scroll.setPreferredSize(new Dimension(600, 400));
marco.add(scroll,gbc);
gbc.insets=new Insets(0,0,0,0);
gbc.gridx=4;
gbc.gridy=2;
gbc.gridwidth=2;
gbc.gridheight=1;
gbc.anchor=GridBagConstraints.SOUTHWEST;
inicioregistro.setFont(new Font("Arial", Font.PLAIN, 32));
marco.add(inicioregistro,gbc);
gbc.gridx=4;
gbc.gridy=3;
gbc.gridwidth=2;
gbc.gridheight=1;
tf_inicioregistro.setEditable(false);
tf_inicioregistro.setFont(new Font("Arial", Font.PLAIN, 32));
tf_inicioregistro.setBackground(Color.white);
//tf_inicioregistro.setText("hola");
gbc.anchor=GridBagConstraints.NORTHWEST;
marco.add(tf_inicioregistro,gbc);
gbc.gridx=4;
gbc.gridy=4;
gbc.gridwidth=2;
gbc.gridheight=1;
finregistro.setFont(new Font("Arial", Font.PLAIN, 32));
gbc.anchor=GridBagConstraints.SOUTHWEST;
marco.add(finregistro,gbc);
gbc.gridx=4;
gbc.gridy=5;
gbc.gridwidth=2;
gbc.gridheight=1;
tf_finregistro.setEditable(false);
tf_finregistro.setFont(new Font("Arial", Font.PLAIN, 32));
tf_finregistro.setBackground(Color.white);
gbc.anchor=GridBagConstraints.NORTHWEST;
//tf_inicioregistro.setText("hola");
marco.add(tf_finregistro,gbc);
gbc.gridx=4;
gbc.gridy=6;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.anchor=GridBagConstraints.CENTER;
btn_guardar.setFont(new Font("Arial", Font.PLAIN, 18));
btn_guardar.setText("Guardar");
btn_guardar.setIcon(new ImageIcon("guardar.png"));
btn_guardar.setName("guardar");
btn_guardar.addMouseListener(new Seleccion2());
btn_guardar.setToolTipText("No hay registro para guardar");
btn_guardar.setEnabled(false);
marco.add(btn_guardar,gbc);
gbc.gridx=5;
gbc.gridy=6;
gbc.gridwidth=1;
gbc.gridheight=1;
btn_abrir.setFont(new Font("Arial", Font.PLAIN, 18));
btn_abrir.setText("Abrir");
btn_abrir.setIcon(new ImageIcon("abrir.png"));
btn_abrir.setName("abrir");
//encender.setOpaque(false);
btn_abrir.addMouseListener(new Seleccion2());
marco.add(btn_abrir,gbc);
//gbc.insets=new Insets(5,5,5,5);
gbc.gridx=0;
gbc.gridy=7;
gbc.gridwidth=4;
gbc.gridheight=1;
//gbc.fill=GridBagConstraints.HORIZONTAL;
txt_noticias.setEditable(false);
txt_noticias.setBackground(Color.white);
marco.add(txt_noticias,gbc);
gbc.gridx=4;
gbc.gridy=7;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.weightx=1;
//gbc.fill=GridBagConstraints.NONE;
pnl_puerto.add(lbl_puerto,gbc);
cmb_puerto.addItemListener(new PORTlistener());
pnl_puerto.add(cmb_puerto,gbc);
pnl_puerto.setOpaque(false);
marco.add(pnl_puerto,gbc);
gbc.gridx=5;
gbc.gridy=7;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.weightx=1;
gbc.fill=GridBagConstraints.NONE;
pnl_baudrate.add(lbl_baudrate,gbc);
cmb_baudrate.addItemListener(new BRlistener());
pnl_baudrate.add(cmb_baudrate,gbc);
pnl_baudrate.setOpaque(false);
marco.add(pnl_baudrate,gbc);



marco.pack();
marco.setVisible(true);
marco.setLocationRelativeTo(null);
}
class Seleccion extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
Component aux=e.getComponent();
}
public void mouseReleased(MouseEvent e)
{
Component aux=e.getComponent();
{
if(aux.getName().equals("encender")&&encendido==1)
{
encender.setIcon(new ImageIcon("redbutton.png"));
onoff.setText("OFF");
onoff.setForeground(Color.red);
encendido=0;
conectado=0;
serialPort.removeEventListener();
serialPort.close();
txt_noticias.setText("Conexión interrumpida");
tf_finregistro.setText(hora.getText());
btn_abrir.setToolTipText("Abrir registro previo");
btn_abrir.setEnabled(true);
btn_guardar.setToolTipText("Guardar registro");
btn_guardar.setEnabled(true);
}
else if(aux.getName().equals("encender")&&encendido==0)
{
initialize();
if(conectado==1)
{
encender.setIcon(new ImageIcon("greenbutton.png"));
onoff.setText("ON");
onoff.setForeground(Color.green);
encendido=1;
tf_inicioregistro.setText(hora.getText());
tf_finregistro.setText("En ejecución...");
btn_abrir.setToolTipText("Para abrir un registro debe apagar la lectura de tarjetas");
btn_abrir.setEnabled(false);
btn_guardar.setToolTipText("Para guardar un registro debe apagar la lectura de tarjetas");
btn_guardar.setEnabled(false);
}

}

}
}
}

class Seleccion2 extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
Component aux=e.getComponent();
}
public void mouseReleased(MouseEvent e)
{
Component aux=e.getComponent();
{
if(aux.getName().equals("abrir"))
{
FileChooser fileChooser = new FileChooser();
selectedFile = fileChooser.showOpenDialog(null);
if (selectedFile != null) {
txt_noticias.setText("Fichero Seleccionado: " + selectedFile.getName());
nombrearchivo=selectedFile.getAbsolutePath();
Leer(nombrearchivo);
//System.out.println(nombrearchivo);
}
else {
txt_noticias.setText("Cancelada selección de fichero");
}
}
if(aux.getName().equals("guardar"))
{
FileChooser fileChooser = new FileChooser();
savedFile = fileChooser.showSaveDialog(null);
if (savedFile != null)
{
infile=savedFile.getAbsolutePath();
Crear(infile);
txt_noticias.setText("Guardado como: " + savedFile.getName());
}
else {
txt_noticias.setText("Cancelado guerdado de fichero");
}
}

}
}
}

class BRlistener implements ItemListener
{
@Override
public void itemStateChanged(ItemEvent evt)
{
// TODO Auto-generated method stub
JComboBox cb = (JComboBox) evt.getSource();
String str_DATA_RATE = (String) cb.getSelectedItem();
DATA_RATE=Integer.parseInt(str_DATA_RATE);
txt_noticias.setText("BaudRate cambiado a: "+str_DATA_RATE);
}

}

class PORTlistener implements ItemListener
{
@Override
public void itemStateChanged(ItemEvent evt)
{
// TODO Auto-generated method stub
JComboBox cb = (JComboBox) evt.getSource();
String str_PORT_NAME = (String) cb.getSelectedItem();
PORT_NAME=str_PORT_NAME;
txt_noticias.setText("Puerto cambiado a: "+str_PORT_NAME);
}

}

class MiTarea extends TimerTask
{
public void run()
{
Date date = new Date();
SimpleDateFormat ft=new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
hora.setText(ft.format(date));
}// end run()
}

public void initialize()
{

CommPortIdentifier portId = null;
Enumeration portEnum = CommPortIdentifier.getPortIdentifiers();

// iterate through, looking for the port
while (portEnum.hasMoreElements())
{
CommPortIdentifier currPortId = (CommPortIdentifier)
portEnum.nextElement();

if (PORT_NAME.equals(currPortId.getName()))
{
portId = currPortId;

break;
}
}
if (portId == null)
{
System.out.println("Could not find COM port.");
txt_noticias.setText("Conexión fallida");
encendido=0;
conectado=0;
return;
}
else
{
conectado=1;
}
try
{
// open serial port, and use class name for the appName.
serialPort = (SerialPort) portId.open(this.getClass().getName(),
TIME_OUT);
// set port parameters
serialPort.setSerialPortParams(DATA_RATE,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
// open the streams
input = new BufferedReader(new InputStreamReader(serialPort.getInputStream()));
output = serialPort.getOutputStream();
// add event listeners
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
//lbl_conectar.setIcon(new ImageIcon("lightON.png"));
txt_noticias.setText("Conexión establecida");
}
catch (Exception e)
{
System.err.println(e.toString());
}
}

/**
* Handle an event on the serial port. Read the data and print it.
*/
public synchronized void serialEvent(SerialPortEvent oEvent)
{
if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE)
{
try
{
String inputLine=input.readLine();
//if(inputLine.trim().equals("25 9A 0F 11"))
//txt_noticias.setText("wololo");
//texto.append("Entrada: " + inputLine + "; " + hora.getText() + "\n");
for(int i=0;i<2;i++)
{
//texto.append(Integer.toString(i));
//texto.append(inputLine);

if(Objects.equals(personal[i][0],inputLine.trim()) && personal[i][1].equals("out"))
{
personal[i][1]="in";
texto.append("Entrada: " + inputLine + "; " + hora.getText() + "\r\n");
}
else if(Objects.equals(personal[i][0],inputLine.trim()) && personal[i][1].equals("in"))
{
personal[i][1]="out";
texto.append("Salida: " + inputLine + "; " + hora.getText() + "\r\n");
}


}
} catch (Exception e) {
System.err.println(e.toString());
}
}
}

public void Leer(String archivosalida)
{
try
{
FileReader fr=new FileReader(nombrearchivo);
BufferedReader textReader=new BufferedReader(fr);

int numberOfLines=readLines();
String[] textData=new String[numberOfLines];

int i;
for(i=0; i<numberOfLines;i++)
{
textData[i]=textReader.readLine();
System.out.println(textData[i]);
//texto.append(textData[i]+"\r\n");

}
if(textData[0].indexOf(':')>-1&&textData[1].indexOf(':')>-1)
{
if(numberOfLines>1 && Objects.equals(textData[0].substring(0, textData[0].indexOf(':')),"Inicio de registro mostrado") && Objects.equals(textData[1].substring(0, textData[1].indexOf(':')),"Fin de registro mostrado") )
{
tf_inicioregistro.setText(textData[0].substring(textData[0].indexOf(":")+1));
tf_finregistro.setText(textData[1].substring(textData[1].indexOf(":")+1));
for(i=2; i<numberOfLines;i++)
{
texto.append(textData[i]+"\r\n");
}
txt_noticias.setText("Registro leído correctamente");
}
else
{
txt_noticias.setText("Formato de archivo incorrecto");
}
}
else
{
txt_noticias.setText("Formato de archivo incorrecto");
}

textReader.close();
}

catch(IOException e)
{
}
}

int readLines() throws IOException
{
FileReader file_to_read=new FileReader(nombrearchivo);
BufferedReader bf=new BufferedReader(file_to_read);
String aLine;
int numberOfLines=0;
while((aLine=bf.readLine())!=null)
{
numberOfLines++;
}
bf.close();
return numberOfLines;
}

public void Crear(String archivoentrada)
{
try
{
Writer output = null;
File file = new File(archivoentrada);
output = new BufferedWriter(new FileWriter(file,true));
output.append("Inicio de registro mostrado: " + tf_inicioregistro.getText() + "\r\n");
output.append("Fin de registro mostrado: " + tf_finregistro.getText() + "\r\n");
output.append(texto.getText());
output.close();
}
catch(IOException e)
{
}
}

public static void main(String[] args)
{
MaquinaFichar app=new MaquinaFichar();
}

}


In order to implement a modern file chooser/opener, I created the following class:

package fichajes;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.paint.Color;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.geometry.Pos;
import javafx.geometry.Insets;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import java.io.File;
import java.util.List;
public class SelectFileChooserExample
extends Application {
private Text actionStatus;
private Stage savedStage;
private static final String titleTxt = "JavaFX File Chooser Example 1";
/*public static void main(String [] args) {
Application.launch(args);
}*/
@Override
public void start(Stage primaryStage) {

primaryStage.setTitle(titleTxt);
// Window label
Label label = new Label("Select File Choosers");
label.setTextFill(Color.DARKBLUE);
label.setFont(Font.font("Calibri", FontWeight.BOLD, 36));
HBox labelHb = new HBox();
labelHb.setAlignment(Pos.CENTER);
labelHb.getChildren().add(label);
// Buttons
Button btn1 = new Button("Choose a file...");
btn1.setOnAction(new SingleFcButtonListener());
HBox buttonHb1 = new HBox(10);
buttonHb1.setAlignment(Pos.CENTER);
buttonHb1.getChildren().addAll(btn1);
Button btn2 = new Button("Choose multiple PDF files...");
btn2.setOnAction(new MultipleFcButtonListener());
HBox buttonHb2 = new HBox(10);
buttonHb2.setAlignment(Pos.CENTER);
buttonHb2.getChildren().addAll(btn2);
// Status message text
actionStatus = new Text();
actionStatus.setFont(Font.font("Calibri", FontWeight.NORMAL, 20));
actionStatus.setFill(Color.FIREBRICK);
// Vbox
VBox vbox = new VBox(30);
vbox.setPadding(new Insets(25, 25, 25, 25));;
vbox.getChildren().addAll(labelHb, buttonHb1, buttonHb2, actionStatus);
// Scene
Scene scene = new Scene(vbox, 500, 300); // w x h
primaryStage.setScene(scene);
primaryStage.show();
savedStage = primaryStage;
}
private class SingleFcButtonListener implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent e) {
showSingleFileChooser();
}
}
private void showSingleFileChooser() {

FileChooser fileChooser = new FileChooser();
File selectedFile = fileChooser.showOpenDialog(null);
if (selectedFile != null) {
actionStatus.setText("File selected: " + selectedFile.getName());
}
else {
actionStatus.setText("File selection cancelled.");
}
}
private class MultipleFcButtonListener implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent e) {
showMultipleFileChooser();
}
}
private void showMultipleFileChooser() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select PDF files");
fileChooser.setInitialDirectory(new File("X:\\testdir\\two"));
fileChooser.getExtensionFilters().addAll(
new ExtensionFilter("PDF Files", "*.pdf"));
List<File> selectedFiles = fileChooser.showOpenMultipleDialog(savedStage);
if (selectedFiles != null) {
actionStatus.setText("PDF Files selected [" + selectedFiles.size() + "]: " +
selectedFiles.get(0).getName() + "..");
}
else {
actionStatus.setText("PDF file selection cancelled.");
}
}
}

Try the Whole Setup

Reloj de fichar con tarjetas RFID // Punch clock with RFID cards

Here you have a demo of the project. Should you have any doubt or difficulty to reach an optimal result, you can contact me.