IoT Based Fingerprint Attendance System Using Evive (Arduino Based Embedded Platform)

Now, you can easily track attendance and clock working hours with the help of this easy and fun to make DIY fingerprint scanner! We’ll be using ThingSpeak to store and retrieve the data. All you require is a cardboard piece, some colored paper, a fingerprint sensor, evive, and lots of DIYing!

Ready for some scanning and tracking? Hop on board!

Step 1: Components Required

Hardware

  • evive / Arduino Board
  • Fingerprint Sensor
  • WiFi Module (ESP8266)
  • Jumper Cables
  • Cardboard
  • Color Papers
  • Hot Glue

Software

Step 2: Making the Evive Holder

Take the piece of cardboard and cut it in the shape of evive or your Arduino Board.

The box I have made just needs to be folded and glued.

Cut out the extras and you will get the shape as shown in Image2.

To fold the cardboard, make slight cuts at the edges drawn. Thus, helping you to fold it into the box easily.

Finally, to keep the box stable, glue the edges using Hot Glue.

Thus, our box is almost ready.

Step 3: Decorating the Box

Take the box that, you have made and color it according to your choice.

I have glued the color paper on the box to make it attractive.

Finally, keep the evive safe 🙂 I have added a lock to it.

I took a small string and have made a loop of it.

I have fixed a small bolt onto the side of the box to fix the loop around it.

Finally, as we are using the fingerprint sensor. I have 3D printed a sensor holder and have attached it to the front door of the box.

Step 4: Circuitry

Once done with the making, it’s time to make the connection:

  • VCC (Red wire) to evive’s 3.3 V
  • GND (Black wire) to evive’s GND
  • Rx (Yellow wire) to Digital Pin 10 of evive
  • Tx (White wire) to Digital Pin 11 of evive

Step 5: Working Modes

I have added the two working mode for the attendance system:

  1. Enroll Mode
  2. Attendance Mode

Let’s discuss each mode in detail.

Step 6: Enroll Mode

All the first time user needs to first enroll themselves into the system.

I have added some instructions so that one can enroll the fingerprints easily.

I have added the following instructions:

  1. Choose the Enroll Mode
  2. Choose the Number on which you wanna enroll yourself.
  3. Place your finger on the sensor.
  4. Then remove the finger and again place it for the verification
  5. If the fingerprints are matched, you have now enrolled.
  6. If not, have to follow the above steps again.

Step 7: Attendance Mode

Once you have enrolled yourself, all you need to do is mark your attendance daily to check your working hours.

Select the Attendance mode and place your finger on the sensor and mark your attendance.

You can select the text you want to display on the screen.

Once you have marked the attendance, on selecting the attendance mode again and placing your finger again, will mark your exit.

You can again choose the exit text you want to display.

Step 8: Storing Data

I have used Thingspeak, which is the opensource software to store and retrieve data.

I have first created a Thingspeak account. One out of the many good things about Thingspeak is that you can store the data coming from your multiple projects in a single account. All you need to do is create a different channel for each project. You can store up to 8 fields on 1 channel. We will store 1 parameter.

When a new channel is created, you can see four graphs for each parameter. Get the Channel ID of your Channel (To be used while connecting to the channel). Get the Read and Write API from the API tab:

To connect your project with the corresponding channel, you need to enter the Thingspeak’s API of that channel into your Arduino Code.

All our records are being directly stored over WiFi to our account.

You can retrieve the data by downloading the CSV File.

Step 9: Arduino Code

Upload the following Arduino code to evive.

//——————-Library Declaration———————–
#include
#include
#include
#include // Library Declaration
#include
#include

//——————Declaration for fingerprint sensor———
uint16_t id ; // variable for storing id
unsigned int total_count =0; // variable for storing total count
unsigned int buzzer = 46;
unsigned int state[25]={};
String UserName = “”; // variable for storing names
String Time_State = “”; // variable for Date and Time

SoftwareSerial mySerial(10, 11); // sensor Rx pin - 10 , Tx pin- 11 // if using arduino then change this pins
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);

//——————- declaration for ESP8266——————
#define SSID “AR3” // Enter your WiFi name here
#define PASSWORD “STEM@101” // Enter your Wifi password here
#define HOST_NAME “api.thingspeak.com”
#define KEY “G8WX13SFJTFN16ER” // Enter your thingspeak api key here
#define HOST_PORT (80)

ESP8266 wifi(Serial3, 115200); // library function

//——————-Declaration for real time clock———–
Rtc_Pcf8563 rtc;

////////////////////////////////////////////////////////////////////////////
void setup()
{
// put your setup code here, to run once:
Serial.begin(9600);
finger.begin(57600);
pinMode(buzzer,OUTPUT);
INIT_TFT();
Rtc_init();
for(unsigned int i=0;i<25;i++)
{
state[i]=0;
}
//—————————Setup for Fingerprint sensor——————-
if (finger.verifyPassword())
{
Serial.println(“Found fingerprint sensor!”);
}
else
{
Serial.println(“Did not find fingerprint sensor :(“);
while (1)
{
delay(1);
}
}

  finger.getTemplateCount();
  Serial.print("Sensor contains ");
  Serial.print(finger.templateCount);
  Serial.println(" templates");

//————————— setup for esp8266 ————————–

 if (wifi.setOprToStation())
     {
       Serial.print("set to station mode\r\n");
     }
 else
     {
       Serial.print("error in setting to station mode\r\n"); 
     }

 if (wifi.joinAP(SSID, PASSWORD)) 
     {
        Serial.print("Join AP success\r\n");
        Serial.print("IP: ");
        Serial.println(wifi.getLocalIP().c_str());
     }
 else
     {
        Serial.print("Join AP failure\r\n");
     }
 wifi.disableMUX();
 Serial.print("setup end\r\n");
 delay(100);
 digitalWrite(13,HIGH);

}

///////////////////////////////////////////////////////////////////////////////////////
void loop()
{
// put your main code here, to run repeatedly:

  START_TFT();
  while(!((digitalRead(40)==HIGH)||(digitalRead(41)==HIGH)))
  {
    Delay(1);
  }

////////////////////// Attendance mode ////////////////////////////////
if(digitalRead(40)==HIGH)
{
ATTENDANCE_BASIC_TFT();
while(digitalRead(40)==HIGH)
{
Attendance_mode();
}
}

///////////////////// Enroll mode ///////////////////////////////////
else if(digitalRead(41)==HIGH)
{
while(digitalRead(41)==HIGH)
{
Enroll_mode();
}
}

}

//////////////////////////////////TFT Function////////////////////////////////////////

void INIT_TFT()
{
tft_init(INITR_BLACKTAB);
tft.setRotation(1);
tft.fillScreen(ST7735_BLACK);
}

void START_TFT()
{
tft.fillScreen(ST7735_BLACK);
tft.setCursor(25,25);
tft.setTextSize(2);
tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
tft.print(“STEMpedia”);

  tft.setCursor(28,50);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.print("ATTENDANCE SYSTEM");

  tft.setCursor(5,70);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
  tft.print("SS1 UP  - ATTENDANCE MODE");

  tft.setCursor(5,80);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
  tft.print("SS1 DOWN - ENROLL MODE");
  tft.setCursor(20,100);
  tft.print("(SS1 - SlideSwitch 1)");

}

void ATTENDANCE_BASIC_TFT()
{

  tft.fillScreen(ST7735_BLACK);
  tft.drawRoundRect(10,40,130,75,5,ST7735_WHITE);

  tft.setCursor(40,50);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("INSTRUCTION");

  tft.setCursor(35,30);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.print("ATTENDANCE MODE");

}

void ENROLL_BASIC_TFT()
{
tft.fillScreen(ST7735_BLACK);
tft.setCursor(45,30);
tft.setTextSize(1);
tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
tft.print(“ENROLL MODE”);

  tft.drawRoundRect(10,40,130,75,5,ST7735_WHITE);
  tft.setCursor(40,50);
  tft.setTextColor(ST7735_GREEN,ST7735_BLACK);
  tft.setTextSize(1);
  tft.print("INSTRUCTION");

}

///////////////////////////////////Function for fingerprint sensor//////////////////////////////////

uint8_t getFingerprintID()
{
uint8_t p = finger.getImage();
switch (p)
{
case FINGERPRINT_OK:
Serial.println(“Image taken”);
break;
case FINGERPRINT_NOFINGER:
Serial.println(“No finger detected”);
return p;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.println(“Communication error”);
return p;
case FINGERPRINT_IMAGEFAIL:
Serial.println(“Imaging error”);
return p;
default:
Serial.println(“Unknown error”);
return p;
}

      // OK success!  

      p = finger.image2Tz();
      switch (p)
          {
             case FINGERPRINT_OK:
                         Serial.println("Image converted");
                         break;
             case FINGERPRINT_IMAGEMESS:
                         Serial.println("Image too messy");
                         return p;
             case FINGERPRINT_PACKETRECIEVEERR:
                         Serial.println("Communication error");
                         return p;
             case FINGERPRINT_FEATUREFAIL:
                         Serial.println("Could not find fingerprint features");
                         return p;
             case FINGERPRINT_INVALIDIMAGE:
                         Serial.println("Could not find fingerprint features");
                         return p;
             default:
                         Serial.println("Unknown error");
                         return p;
            }

   // OK converted!
   p = finger.fingerFastSearch();
          if (p == FINGERPRINT_OK)
               {
                  Serial.println("Found a print match!");
               } 
          else if (p == FINGERPRINT_PACKETRECIEVEERR)
               {
                   Serial.println("Communication error");
                   return p;
               } 
          else if (p == FINGERPRINT_NOTFOUND) 
               {
                   Serial.println("Did not find a match");
                   return p;
               } 
          else 
               {
                   Serial.println("Unknown error");
                   return p;
               }   
          return finger.fingerID;

}

int getFingerprintIDez()
{
if(digitalRead(40)==LOW)
{
return;
}

       uint8_t p = finger.getImage();
       Delay(1);
       if (p != FINGERPRINT_OK) 
         {
           return -1;
         }
       p = finger.image2Tz();
       if (p != FINGERPRINT_OK)  
        {
          return -1;
        }
       p = finger.fingerFastSearch();
       if (p != FINGERPRINT_OK)  
        {
           ENROLL_BASIC_TFT();
           tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
           tft.setTextSize(1);
           tft.setCursor(25,75);
           tft.print("  Access Denied");
           Access_denied_beeps();
           Delay(20);
           return -1;
        }
     ATTENDANCE_BASIC_TFT();
     UserName = NUM_TO_NAME(finger.fingerID);
     if(state[finger.fingerID-1]==0)
         {
           tft.setTextSize(1);
           tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
           tft.setCursor(25,75);
           tft.print("  Welcome "+UserName);
           tft.setCursor(20,85);
           tft.print(" Have a good day!");
           Access_granted_beeps();
           Delay(150);
           ATTENDANCE_BASIC_TFT();
           tft.setTextSize(1);
           tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
           tft.setCursor(25,75);
           tft.print("Attendance Marked");
           Delay(50);
        }
      else if(state[finger.fingerID-1]==1)
        {
           tft.setTextSize(1);
           tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
           tft.setCursor(25,75);
           tft.print("    Bye "+UserName);
           tft.setCursor(20,85);
           tft.print("   See you soon!");
           Access_granted_beeps();
           Delay(10);
        }

     STATE(finger.fingerID);
     SEND_DATA();
     return finger.fingerID; 
}

uint8_t getFingerprintEnroll()
{
int p = -1;
while (p != FINGERPRINT_OK)
{
Delay(1);
p = finger.getImage();
switch (p)
{
case FINGERPRINT_OK:
Serial.println(“Image taken”);
break;
case FINGERPRINT_NOFINGER:
Serial.println(“.”);
Delay(1);
if(digitalRead(41)==LOW)
{
return;
}
break;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.println(“Communication error”);
break;
case FINGERPRINT_IMAGEFAIL:
Serial.println(“Imaging error”);
break;
default:
Serial.println(“Unknown error”);
break;
}
}

  // OK success!

 p = finger.image2Tz(1);
   switch (p)
       {
          case FINGERPRINT_OK:
                   Serial.println("Image converted");
                   break;
          case FINGERPRINT_IMAGEMESS:
                   Serial.println("Image too messy");
                   return p;
          case FINGERPRINT_PACKETRECIEVEERR:
                   Serial.println("Communication error");
                   return p;
          case FINGERPRINT_FEATUREFAIL:
                   Serial.println("Could not find fingerprint features");
                   return p;
          case FINGERPRINT_INVALIDIMAGE:
                   Serial.println("Could not find fingerprint features");
                   return p;
          default:
                   Serial.println("Unknown error");
                   return p;
        }

     Serial.println("Remove finger");
     ENROLL_BASIC_TFT();
     tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
     tft.setTextSize(1);
     tft.setCursor(25,75);
     tft.print("Remove Your Finger");
     Delay(150);
     p = 0;
     while (p != FINGERPRINT_NOFINGER)
         {
            Delay(1);
            p = finger.getImage();
         }
     Serial.print("ID ");
     Serial.println(id);
     p = -1;
     Serial.println("Place the same finger ");

     ENROLL_BASIC_TFT();
     tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
     tft.setTextSize(1);
     tft.setCursor(30,75);
     tft.print("Place the same ");
     tft.setCursor(35,85);
     tft.print("finger again");
     Delay(50);

     while (p != FINGERPRINT_OK)
         {
             p = finger.getImage();
             switch (p) 
                {
                   case FINGERPRINT_OK:
                                Serial.println("Image taken");
                                 break;
                   case FINGERPRINT_NOFINGER:
                                Serial.print(".");
                                break;
                   case FINGERPRINT_PACKETRECIEVEERR:
                                Serial.println("Communication error");
                                break;
                   case FINGERPRINT_IMAGEFAIL:
                                Serial.println("Imaging error");
                                 break;
                   default:
                                Serial.println("Unknown error");
                                break;
               }
       }

     // OK success!

     p = finger.image2Tz(2);
     switch (p)
         {
             case FINGERPRINT_OK:
                        Serial.println("Image converted");
                        break;
             case FINGERPRINT_IMAGEMESS:
                        Serial.println("Image too messy");
                        return p;
             case FINGERPRINT_PACKETRECIEVEERR:
                         Serial.println("Communication error");
                         return p;
             case FINGERPRINT_FEATUREFAIL:
                         Serial.println("Could not find fingerprint features");
                         return p;
             case FINGERPRINT_INVALIDIMAGE:
                         Serial.println("Could not find fingerprint features");
                         return p;
             default:
                         Serial.println("Unknown error");
                         return p;
         }

       // OK converted!
       Serial.print("Creating model for #");
       Serial.println(id);

       p = finger.createModel();
       if (p == FINGERPRINT_OK) 
            {
               Serial.println("Prints matched!");    
               ENROLL_BASIC_TFT();
               tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
               tft.setTextSize(1);
               tft.setCursor(35,75);
               tft.print("Print matched"); 
               Delay(250);     
            }
      else if (p == FINGERPRINT_PACKETRECIEVEERR) 
            {
               Serial.println("Communication error");
               return p;
            }
      else if (p == FINGERPRINT_ENROLLMISMATCH)
           {
               Serial.println("Fingerprints did not match");
               ENROLL_BASIC_TFT();
               tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
               tft.setTextSize(1);
               tft.setCursor(40,75);
               tft.print("Enrollement ");
               tft.setCursor(35,85);
               tft.print("Unsuccessfull");
               Delay(250);
               return p;
            }
      else 
           {
              Serial.println("Unknown error");
              return p;
           }   

      Serial.print("ID ");
      Serial.println(id);
      p = finger.storeModel(id);
      if (p == FINGERPRINT_OK)
         {
            Serial.println("Stored!");
            ENROLL_BASIC_TFT();
            tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
            tft.setTextSize(1);
            tft.setCursor(40,75);
            tft.print("Enrolled");
            tft.setCursor(35,85);
            tft.print("Successfully");
            Delay(250);
         } 
      else if (p == FINGERPRINT_PACKETRECIEVEERR)
         {
             Serial.println("Communication error"); 
             return p;
         } 
     else if (p == FINGERPRINT_BADLOCATION)
         {
             Serial.println("Could not store in that location");
             return p;
         } 
     else if (p == FINGERPRINT_FLASHERR)
         {
             Serial.println("Error writing to flash");
             return p;
         } 
     else 
        {
            Serial.println("Unknown error");
            return p;
        }   
}

/////////////////////////Function for selecting Number///////////////////////////////////

uint16_t readnumber(void)
{
uint16_t num = 0;
while(digitalRead(42)==HIGH)
{
if(digitalRead(40)==HIGH || digitalRead(41)==LOW)
{
return;
}
num = analogRead(A9);
num = map(num ,0,1023,1,25);
tft.setCursor(60,90);
tft.setTextSize(2);
tft.print(num);
tft.print(” “);
Delay(1);
}
return num;
}
///////////////////////////Function for Number to name conversion////////////////////////

String NUM_TO_NAME(uint16_t number)
{
String Name =””;
switch(number)
{
case 1:
Name = “Punit”;
return Name;
case 2:
Name = “palak”;
return Name;
case 3:
Name = “jennifer”;
return Name;
case 4:
Name = “Jaydeep”;
return Name;
case 5:
Name = “Sagar”;
return Name;
case 6:
Name = “Mimansa”;
return Name;
case 7:
Name = “User7”;
return Name;
case 8:
Name = “User8”;
return Name;
case 9:
Name = “User9”;
return Name;
case 10:
Name = “User10”;
return Name;
case 11:
Name = “User11”;
return Name;
case 12:
Name = “User12”;
return Name;
case 13:
Name = “User13”;
return Name;
case 14:
Name = “User14”;
return Name;
case 15:
Name = “User15”;
return Name;
case 16:
Name = “User16”;
return Name;
case 17:
Name = “User17”;
return Name;
case 18:
Name = “User18”;
return Name;
case 19:
Name = “User19”;
return Name;
case 20:
Name = “User20”;
return Name;
case 21:
Name = “User21”;
return Name;
case 22:
Name = “User22”;
return Name;
case 23:
Name = “User23”;
return Name;
case 24:
Name = “User24”;
return Name;
case 25:
Name = “User25”;
return Name;
}
}

//////////////////////// Function for RTC Clock /////////////////////////////////////////
void Rtc_init()
{
rtc.initClock();
rtc.setDate(13,4,3,0,19);
rtc.setTime(15,30,0);
delay(2000);
}

///////////////////////// Function for Time state ///////////////////////////////////////
void STATE(uint16_t number)
{
rtc.getTime();
rtc.getDate();
if(state[finger.fingerID-1]==0)
{
Time_State= “Entering Time”;
state[finger.fingerID-1]=1;
}
else if(state[finger.fingerID-1]==1)
{
Time_State= “leaving Time”;
state[finger.fingerID-1]=0;
}

}

void Delay(unsigned int wait)
{
for(unsigned int i=0;i<wait;i++)
{
// delay(1);
print_date();
}

}

void print_date()
{
rtc.getTime();
rtc.getDate();

  tft.setCursor(5,10);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
  tft.print(rtc.formatDate(RTCC_DATE_WORLD));

  tft.setCursor(120,10);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
  tft.print(rtc.formatTime(RTCC_TIME_HM));

}

void Access_granted_beeps()
{
beeps(200);
beeps(200);
}

void Access_denied_beeps()
{
for(unsigned int i=0;i<5;i++)
{
beeps(100);
beeps(100);
delay(50);
}

}

void beeps(unsigned int wait)
{
analogWrite(buzzer,20);
delay(wait);
analogWrite(buzzer,0);
delay(wait);
}

////////////////////////// Function for sending data to Internet //////////////////////////////////
void SEND_DATA()
{
uint8_t buffer[1024] = {0};
if (wifi.createTCP(HOST_NAME, HOST_PORT))
{
Serial.print(“create tcp ok\r\n”);
}
else
{
Serial.print(“create tcp err\r\n”);
return;
}
String http = String();
http += “GET /update?key=”;
http += KEY;
http += “&field1=” + UserName;
http += “&field2=” + String(String(rtc.getDay())+”|”+String(rtc.getMonth())+”|”+String(rtc.getYear()));
http += “&field3=” + String(rtc.formatTime());
http += “&field4=” + Time_State;
http += ” HTTP/1.1\r\n”;
http += “Host: api.thingspeak.com\r\n”;
http += “Connection: close\r\n\r\n”;

       Serial.println(http);
       wifi.send((const uint8_t*)http.c_str(), http.length());

       uint32_t len = wifi.recv(buffer, sizeof(buffer), 10000);
       if (len > 0) 
          {
             Serial.print("Received:[");
             for (uint32_t i = 0; i < len; i++)
                 {
                   Serial.print((char)buffer[i]);
                 }
             Serial.print("]\r\n");
           }
       if (wifi.releaseTCP())
       Serial.print("release tcp ok\r\n");
       else
       Serial.print("release tcp err\r\n");
       Delay(20);
 }

void Attendance_mode()
{
tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(20,85);
tft.print(” “);
tft.setCursor(25,75);
tft.print(“Place Your Finger”);
getFingerprintIDez();
Delay(50);
}

void Enroll_mode()
{
ENROLL_BASIC_TFT();
tft.setCursor(15,75);
tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
tft.print(” Slide Slideswitch2″);
tft.setCursor(60,85);
tft.print(” \”UP\” “);
while(!(digitalRead(42)==HIGH))
{
Delay(2);
if(digitalRead(40)==HIGH || digitalRead(41)==LOW)
{
return;
}
}
tft.fillScreen(ST7735_BLACK);
ENROLL_BASIC_TFT();
tft.setCursor(15,75);
tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
tft.setTextSize(1);
tft.print(” Use Potentiometer1″);
tft.setCursor(15,85);
tft.print(” to Select Number”);
Delay(80);
ENROLL_BASIC_TFT();
tft.setCursor(15,65);
tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
tft.print(” Slide Slideswitch2″);
tft.setCursor(60,75);
tft.print(“\”DOWN\””);
id = readnumber();
ENROLL_BASIC_TFT();
tft.setTextColor(ST7735_WHITE,ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(25,75);
tft.print(“Place Your Finger”);
while (!getFingerprintEnroll())
{
Delay(1);
if(digitalRead(40)==HIGH || digitalRead(41)==LOW)
{
return;
}
}
}

Step 10: Conclusion

With this, your DIY fingerprint attendance system is all set to maintain a record for you of all those you came and went.

BTW, did you know that is one door is closed, another somewhere is open? If you didn’t you will now! Owing to overwhelming success and the love and support of DIY enthusiasts and hobbyists around the world, we’ve decided to EXTEND our Indiegogo campaign! So, If you missed the opportunity before, you have the chance to take its advantage NOW! Go check out all the cool stuff that we’re offering HERE and seize the day. 😉

Source: IoT Based Fingerprint Attendance System Using Evive (Arduino Based Embedded Platform)


About The Author

Muhammad Bilal

I am a highly skilled and motivated individual with a Master's degree in Computer Science. I have extensive experience in technical writing and a deep understanding of SEO practices.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top