Home security: access control with ESP8266, RFID and Telegram

Introduction

In the digital age we live in, home security achieved through effective access control has become a priority for many. With the advancement of technologies, it is possible to create intelligent and customized security solutions that not only improve security but also offer convenience and flexibility. In this article, we will explore an innovative project that combines ESP8266, RFID, Telegram and REST API to create a home access control system.

The ESP8266, a Wi-Fi microcontroller, is the heart of this project, allowing simple and direct communication with the Internet. The RFID module, a contactless identification system, is used for user identification, ensuring safe and convenient access control. Finally, the Telegram bot is integrated to send real-time notifications, keeping users informed about logins and providing an easy way to manage permissions.

This system is not only a way to improve home security, but also an example of how the Internet of Things (IoT) can be used to create customized solutions that address specific security needs. Through the use of REST APIs, the system allows simple and intuitive management of permissions, offering the possibility of easily adding, modifying or deleting users (operations that can also be performed via the Telegram bot).

Throughout this article, we will explore how the project was implemented, how the access control system works, and how the REST API and the Telegram bot were integrated to offer a complete solution. This project represents an example of how innovation can be used to solve common problems in a creative and effective way.

Also in this project we will use the PlatformIO IDE for writing the code.

Description of operation

Our home security project is based on an automated system that integrates advanced technologies to ensure safe and controlled access. Here’s how it works:

  1. System Initialization: The system contains on the SD card a CSV file called auth.txt (which we will initially have to create ourselves) which contains, for each line, the UUID of a user and his access privileges (“g” for granted and “d” for denied) separated by a comma. At first the ESP8266 reads this file and loads it into a dictionary (which we will talk about in one of the following paragraphs).
  1. User Identification: When a badge is brought close to the RFID module, the system reads the UUID (Universally Unique Identifier) ​​of the badge. This UUID is a unique identifier for each badge, used to distinguish users.
  1. Permissions Control: After reading the UUID, the system checks the dictionary to determine whether the user has permission to log in.
  1. Access Decision: Based on the permission associated with the UUID, the system decides whether to trigger the relay, allowing or denying access. If the UUID is associated with a granted permission (“g”), the relay activates, allowing access. If permission is denied (“d”), access is denied. Furthermore, in the first case a green LED lights up while in the second case a red LED lights up. The red LED remains lit even when the system is in the “inactive” state, i.e. it is waiting for an RFID tag to be brought near the reader.
  1. Access Registration: Each login attempt is recorded to a CSV file on the SD card. This provides a time log of access, useful for monitoring and security. For each row, the UUID, time (derived from the RTC module), and whether entry was granted (“g”) or denied (“d”) is recorded.
  2. Organized Structure of Folders and Files: The SD card organizes the information in an intuitive way, dividing it into directories corresponding to the current year and month, with daily files containing the data described in the previous point. So, to give an example, the access records for a certain day (for example January 26, 2024) will be found in the file with path templog/24/01/26
  3. Telegram notifications: At the same time as logging in, the system sends a warning message to the Telegram bot. This allows users to receive real-time notifications about accesses, increasing awareness and security.
  4. Permissions management via REST API and Telegram bot: The system is further integrated with a server that exposes REST APIs for managing permissions. These APIs allow you to get the current date and time, read the permissions file, add a new user with its privileges, modify the privileges of an existing user or delete a user. Through the Telegram bot you can add a new user (UUID) with its privileges or modify the privileges of an existing user, delete a user, read the access privileges of a given user. In both cases (REST API or Telegram bot) the changes to permissions will be permanent as they will be saved to the auth.txt file.

What is RFID technology

Radio-Frequency Identification (RFID) is an automatic identification system that uses electromagnetic fields to transfer data between a reader and an RFID tag. This technology is based on contactless communication and allows the storage and retrieval of information from objects, animals or people equipped with RFID tags. Here are some key points regarding the operation and use of RFID:

  1. Principle of operation:
    • An RFID system is composed of a reader (or interrogator) and one or more RFID tags.
    • The tag contains a chip that stores a unique code or other information.
    • When the tag is in the electromagnetic field of the reader, it receives energy and transmits the stored data to the reader.
  2. Operating frequencies:
    • RFIDs operate at different frequencies, mainly divided into low frequency (LF), high frequency (HF), ultra high frequency (UHF), and very high frequency (VHF).
    • Different frequencies affect the reading distance and ability to penetrate through materials.
  3. Types of RFID tags:
    • Tags can be active (with battery) or passive (without battery).
    • Passive tags are powered by the reader field and have a shorter read distance than active tags.
  4. Common applications:
    • Access control: RFID badge for automatic opening or security authentication.
    • Logistics and traceability: monitoring goods in transit through warehouses and distribution chains.
    • Contactless payments: contactless payment systems such as contactless cards.
  5. Security and privacy:
    • RFID can present security and privacy challenges, with the potential for data to be intercepted and cloned.
    • Various encryption techniques have been developed to protect the information exchanged between readers and tags.
  6. Implementation with ESP8266:
    • The ESP8266 can be used as an RFID reader, interfacing with a compatible RFID module.
    • The information read from the RFID tag can be used to trigger specific actions or access resources.

The use of RFID offers an effective and cost-effective solution for multiple applications, enabling the automation of processes and improving operational efficiency.

The RFID kit used in this project

The kit, in this case distributed by the ICQUANZX company (but also exists under different brands), consists of an RFID reader, a badge, a key ring (always RFID), a connector to be soldered onto the reader and some wires. If you are not familiar with the world of soldering irons and want to try soldering the connector yourself, I recommend you take a look at the article Yet another tutorial on how to solder.

Both the badge and the key fob contain a code that can be read by the RFID reader. In my case the badge has hexadecimal code 73051E99 while the key ring has hexadecimal code A305CD0B.

For the purposes of the project I set the authorization file so that the code A305CD0B can trigger the relay while the code 73051E99 this operation is inhibited. Just to be clearer, I momentarily inserted the SD card into the computer slot, created a file called auth.txt in it and wrote the two lines inside:

A305CD0B,g
73051E99,d

I then safely removed the SD card from the computer and reinserted it into the SD card module connected to the ESP8266.

But how do we find out what UUID codes our tags contain? Initially it is sufficient to keep the auth.txt file empty, then bring a tag close to the reader and read its UUID printed on the Serial Monitor. Repeat the operation for all the tags we have. At this point you can extract the SD card from the module, put it in the computer and compile it with a simple text editor. Alternatively, without extracting the SD card, once the UUIDs have been obtained, the user insertion operations with their privileges provided by the Telegram bot or the REST API can be used.

The RFID reading module can interface with the ESP8266 via both SPI and I2C communication which we will talk about in the next paragraph. The RFID module used has switches that allow you to select the desired interface between SPI, I2C and UART. In this project it will communicate with I2C interface.

Although the kit is equipped with a male connector, I preferred to solder a female connector onto its I2C port as I consider it more convenient. Obviously you can put the connector you prefer.

I could also have used the SPI bus and therefore had both the SD card reader and the RFID reader connected on the same SPI bus. The problem is that unfortunately they are not compatible, in the sense that the model of SD card on SPI bus used does not allow (due to its hardware problem) other SPI devices to function correctly. So he has to be alone on that bus. The solution was to find an RFID reader that also had an I2C interface in order to connect them on different buses. In addition to the RFID reader, the RTC module is also connected to the I2C bus and the two coexist peacefully having different addresses.

In the next photo you can see the complete kit:

Complete PN532 RFID kit
Complete PN532 RFID kit
Detail of the female connector soldered onto the I2C bus
Detail of the female connector soldered onto the I2C bus
Setting for using the I2C bus
Setting for using the I2C bus

As you can see from the previous photo, to be able to set communication on the I2C bus you must move switch 1 to the right and switch 2 to the left. You may find a small, semi-transparent orange film protecting the switch. Remove it before performing the bus type setting operation.

SPI interface

The RFID reader module uses SPI interface, which is a 4-wire serial communication protocol commonly used in embedded projects. These four wires are:

  1. MISO (Master In Slave Out): this is the pin through which the module receives data from the master device, which is usually the Arduino or another microcontroller.
  2. MOSI (Master Out Slave In): this is the pin through which the module sends data to the master device.
  3. SCK (Serial Clock): this is the clock pin that synchronizes data transmission between the module and the master device.
  4. CS (Chip Select): this pin is used to select the RFID module and initialize data sending/receiving operations.

I2C interface

Alternatively the RFID reader module can use the I2C interface. The I2C protocol, also known as Two-Wire Interface (TWI), is a serial communication standard that allows the transmission of data between electronic devices. This protocol is widely used in many applications, including microcontrollers, to facilitate communication between different components of a system. The I2C protocol is characterized by some key characteristics that make it ideal for low power consumption applications with limited hardware resources.

  • Two-way communication: I2C supports two-way communication, allowing one device to send data to another device and vice versa. This feature is particularly useful in systems that require continuous interaction between components.
  • Use of two lines: As the name suggests, I2C uses only two lines for communication: one for clock (SCL) and one for data (SDA). This significantly reduces the number of pins required for communication, making the protocol ideal for devices with limited pin resources.
  • Address management: Each I2C device has a unique address, allowing multiple devices to communicate on the same clock and data line. This addressing system allows for efficient and organized communication between devices. It should be noted that devices with the same address cannot coexist on this bus.
  • Standard speed and low speed communication: I2C supports standard communication speeds up to 100 kHz and low speed communication speeds up to 400 kHz. These speeds are sufficient for most applications, making the protocol suitable for devices that do not require high communication speeds.
  • Compatibility and ease of use: The I2C protocol is widely supported by many platforms and devices, making it a popular choice for communication between components. Its simplicity and the presence of many libraries and documentation make it easy to implement and use.

The I2C protocol is a perfect example of how the design of communication protocols can be simple and powerful, offering an efficient and versatile solution for communication between devices in a wide range of applications.

What is a dictionary

We have already said that in the setup phase the ESP8266 reads the auth.txt authorization file from the SD card and loads it into a dictionary so as to have this information already in memory without having to re-read the file every time.

This file, being a CSV file, will look like this:

A305CD0B,g
73051E99,d

It can be seen that the user with UUID A305CD0B has been associated with the “g” (granted) privilege and will therefore be able to access while access is denied to the user with UUID 73051E99 (“d” for denied).

  1. Dictionary definition:
    • In programming, a dictionary is a data structure that allows you to store key-value pairs.
    • Each dictionary element consists of a unique key associated with a value.
  2. Structure and access:
    • Dictionaries are structured to allow quick access to values ​​via keys.
    • Values ​​are accessed by specifying the associated key, allowing efficient retrieval of information.
  3. Keys and Values:
    • The keys in a dictionary are usually strings, numbers, or other immutable data types.
    • Values ​​can be of any type, including numbers, strings, lists, or even other dictionaries.
  4. Utilities and applications:
    • Dictionaries are widely used to manage structured data and associate information flexibly.
    • They are especially useful when data must be accessed via unique identifiers rather than fixed locations.
  5. Common operations:
    • Adding new key-value pairs: myDictionary[key] = value.
    • Removing a pair: delete myDictionary[key].
    • Retrieving the value associated with a key: value = myDictionary[key].
  6. Iteration:
    • You can iterate over all keys, values, or key-value pairs in the dictionary.
    • This allows you to perform operations on all elements of the dictionary efficiently.
    • It is possible to extract the list of all the keys or all the values ​​present in the dictionary.
  7. Implementation in the ESP8266:
    • In programming contexts for the ESP8266, dictionaries are often used to manage configurations, dynamic key-value associations, or map relevant information.
  8. Efficiency and complexity:
    • The efficiency of accessing data via keys makes dictionaries an ideal choice for situations where fast retrieval of information is necessary.
    • The time complexity for search, insert, and delete operations is often very low compared to other data structures.

Using dictionaries adds a level of flexibility and organization to programs, allowing for efficient and dynamic management of information.

In our case the dictionary loaded from the auth.txt file will look like this:{“A305CD0B” : “g”, “73051E99” : “d”}.

The set of REST APIs available

The device communicates with the outside via a set of 7 REST APIs, 2 of the GET type and 5 of the POST type:

  • getDate (GET) returns a Json document containing the current date and time in the RTC module (year, month, day, hours, minutes, seconds). It is used to check the accuracy of the date and time;
  • readAuth (GET) returns the contents of the auth.txt file containing the permissions for each user;
  • fileExists (POST) returns true or false depending on whether a certain access log file is present in the file system or not. The data to allow it to construct the correct path of the file are year, month, day sent via a special Json of the type:
                      {
                             "year": "24",
                             "month" : "03",
                             "day" : "02"
                       }
  • fileRead (POST) returns the contents of a given access log file. The data to allow it to construct the correct path of the file are year, month, day sent via a special Json of the type:
                      {
                             "year": "24",
                             "month" : "03",
                             "day" : "02"
                       }
  • setDate (POST) adjust the clock with the year, month, day, hours, minutes and seconds data provided with a Json of the type:
                      {
                             "year": "24",
                             "month" : "03",
                             "day" : "02",
                             "hour" : "15",
                             "minutes" : "12",
                             "seconds" : "10"
                       }
  • addModUser (POST) adds a user with its privileges to the auth.txt file or modifies the privileges of an already existing user. A possible example Json is the following:
                      {
                           "uuid" : "73051E99",
                           "auth" : "d"
                       }
  • delUser (POST) delete a user with their privileges from the auth.txt file. A possible example Json is the following:
                      {
                             "uuid" : "73051E99"
                       }

The set of Telegram commands available

The Telegram bot receives a notification every time a login is attempted (i.e. every time an RFID tag is detected by the sensor) indicating the UUID and the access privilege associated with it. But it is also able to modify the permissions contained in the auth.txt file via the following commands:

  • addmod:uuid:permission adds a user with his privileges (g or d) or modifies the privileges of an already existing user (example: /addmod:A305CD0B:d)
  • del:uuid removes a user (example: /del:A305CD0B)
  • readuser:uuid returns the privileges of the specified user (example /readuser:A305CD0B)

What components do we need for RFID access control?

The list of components is not particularly long:

  • a breadboard to connect the ESP8266 NodeMCU to the other components
  • some DuPont wires (male – male, male – female, female – female)
  • a 100Ω resistor
  • an 82Ω resistor
  • a green LED
  • a red LED
  • a PN532 RFID module
  • a module with double/single opto-isolated relay
  • a DS3231 RTC module
  • a micro SD card reading/writing module with SPI interface
  • a micro SD card of no more than 32GB formatted in FAT32
  • and, of course, an ESP8266 NodeMCU!

Let’s now look at these components in more detail.

The double relay module

  1. Power supply:
    • It accepts a wide range of supply voltages, usually between 5V and 12V.
    • The power connector is designed to be easily connected to an external power source, such as a battery or power supply.
  2. Relay:
    • Two relays on board, each with its own electrical contacts: common (COM), normal open (NO) and normal closed (NC).
    • The relay contacts are designed to handle power loads. However, the exact specifications depend on the specific model of the relay module.
  3. Control Inputs:
    • Two control inputs (IN1 and IN2) that can be connected to digital pins of an Arduino-type development board.
    • Activating one of these inputs with a high (or low, as appropriate) logic signal will activate the corresponding relay.
  4. LED indicators:
    • Built-in LED indicators for each relay that indicate activation status (often with colors such as red for activated and off for deactivated).
  5. Arduino compatibility:
    • Designed to be easily integrated with development platforms such as Arduino, making relay control a simple and accessible operation.
  6. Pilotable Loads:
    • Capable of driving a variety of electrical loads such as light bulbs, motors, solenoid valves, and other devices requiring on/off control.
    • The exact load specifications depend on the relay model, but they can often handle loads with alternating voltages up to 250V and currents up to 10A.

These relay modules are widely used in home automation, electronic automation and remote control projects, providing a safe and controlled interface for power devices.

PLEASE NOTE: in this project an opto-isolated double relay module is used but we will only use one of the two relays. So you can also choose a single opto-isolated relay module or use the double one, perhaps activating one of the two relays by a badge and the other relay by another badge or the key fob.

An example of a module with double opto-isolated relay used by RFID access control
An example of a module with double opto-isolated relay used by RFID access control

The DS3231 module

The DS3231 RTC (Real-Time Clock DS3231) is an electronic component widely used to track time in embedded applications. Its I2C (Inter-Integrated Circuit) interface makes it easy to integrate with microcontrollers, such as the Arduino, and other digital devices (such as ESP8266 and ESP32). The one used in this article is produced by the company AZDelivery. Let’s see an accurate description of the DS3231:

Extreme precision

The DS3231 is known for its amazing accuracy in keeping track of time. It has a maximum error of just a few seconds per year, making it ideal for applications requiring accurate time stamps.

I2C interface

The DS3231 RTC communicates via the I2C (or I-squared-C) interface. I2C is a serial communications protocol that allows you to connect multiple devices on a single bus, making the DS3231 ideal for projects that require simple, efficient time management.

Full calendar

In addition to keeping track of time, the DS3231 also manages a full calendar, including days of the week, months and years, even accounting for leap years. This feature makes it useful in applications such as alarm clocks, digital calendars, and real-time clocks.

Built-in EEPROM memory

The DS3231 is equipped with a small EEPROM (Electrically Erasable Programmable Read-Only Memory) that can be used to store additional data. This memory is non-volatile, which means that data remains preserved even in the absence of electrical power.

Configurable alarm

You can set up two separate alarms on the DS3231, allowing the device to generate a stop signal or an alarm signal when certain time conditions are met. This feature is useful in applications such as alarm clocks or time controls.

Integrated temperature

The DS3231 also features a built-in temperature sensor. This sensor can be used to monitor ambient temperature and is especially useful when temperature accuracy is important for an application.

Low power and battery backup

To maintain time accuracy even in the event of a main power failure, the DS3231 can be powered by a backup battery. This battery ensures that the device continues to function and keep track of time even when the main power is cut.

Common applications

The DS3231 is widely used in a wide range of applications, including:

  1. Real Time Clocks (RTC): it is commonly used to add precise timestamping capabilities to devices such as digital watches.
  2. Digital alarm clocks: the DS3231 can be used to create precise alarms that do not need to be reset frequently.
  3. Control of timed devices: it is useful in applications that require scheduled activations or deactivations.
  4. Data logger: can be used to annotate data with timestamps in data logging projects.
  5. Home automation systems: It can be integrated into home automation systems to schedule time-based actions.

In summary, the DS3231 is a highly reliable and accurate component for time and date monitoring in electronic applications. Its I2C interface simplifies integration with a variety of devices, making it a popular choice for microcontroller-based projects.

The micro SD card module

The micro SD card module is an electronic component designed for use with Arduino boards and other compatible development platforms. This module allows you to read and write data to Micro SD and Micro SDHC (TransFlash) memory cards using SPI (Serial Peripheral Interface) communication. The one used in this article is produced by the company AZDelivery.

Here is a detailed description of this module:

Support for Micro SD and Micro SDHC

This module is compatible with both Micro SD cards and Micro SDHC cards, allowing the use of cards with capacities up to 32GB. Micro SD cards are commonly available and offer ample storage capacity for data such as log files, images, audio, or any other information you want to record.

Easy to use

The SD Reader Module is easy to integrate into your Arduino projects. It comes with a pre-installed Arduino library, which makes reading and writing data to micro SD cards much easier. This library allows you to easily access files on the card and perform operations such as creating, reading, modifying and deleting files.

Status LED

The module is equipped with a status LED that indicates when the module is active and communicating with the master device. This LED can be useful for debugging and monitoring read/write operations.

Common applications

This micro SD card reader module is widely used in a number of projects, including:

  • Data logging: to record data from sensors or other sources onto a micro SD card for future analysis.
  • Reading multimedia files: to read audio or image files from micro SD cards for playback or viewing on devices.
  • IoT projects: in Internet of Things (IoT)-based projects to record environmental or sensor data on micro SD cards.
  • Video recording: in video recording systems based on Arduino or similar microcontrollers.

The SD card module is a useful and practical component for projects that require reading and writing data on Micro SD memory cards. Its compatibility with Arduino and other development platforms makes it a valuable addition for projects that require data storage and management. Due to its ease of use and support for large memory cards, it is a popular choice among electronics enthusiasts and developers.

Project implementation

The electrical diagram

Before creating the actual circuit let’s take a look at the pinout of the board:

ESP8266 NodeMCU pinout
ESP8266 NodeMCU pinout

Let’s also see the pinouts of the other components:

The pinout of the RTC module
The pinout of the RTC module
The pinout of the micro SD card module
The pinout of the micro SD card module

Pinout of the PN532 RFID module. On the left side you see the I2C bus, on the top side the SPI bus
Pinout of the PN532 RFID module. On the left side you see the I2C bus, on the top side the SPI bus

Let’s now see the electrical diagram of the project, created as usual with Fritzing:

Complete project
Complete project

It is possible that some modules need some connectors and therefore it is necessary to do some soldering. If you are new to this topic I recommend you read the article Yet another tutorial on how to solder.

As you can see, both the micro SD card module and the relay module are powered by 5V from the Vin terminal while the RTC module, as well as the RFID sensor, is powered by 3.3V from the 3V3 terminal of the ESP8266.

The micro SD card module is connected to the SPI port of the ESP8266 which uses the GPIOs:

  • D8 for CS terminal (Chip Select)
  • D7 for MOSI terminal (Master Out Slave In)
  • D6 for MISO terminal (Master In Slave OUT)
  • D5 for the SCK terminal (Serial Clock)

The RTC module and the RFID module are connected to the I2C port of the ESP8266 which uses the pins:

  • D1 for the SCL terminal
  • D2 for the SDA terminal

The control input of the IN1 relay module is connected to pin D3 while the two pins Vin and GND of the ESP8266 NodeMCU are used to power the dual relay module, connecting the Vin pin (NodeMCU side) to the VCC pin (dual relay module side relay) and the two GND (ground) pins.

You will notice that on the relay module there is a jumper (drawn in blue on the left connector) that connects the JD-VCC and VCC terminals. This jumper is used to power the relay module through the VCC and GND terminals on the right connector. Without this jumper, we would be forced to power the module with an external power supply.

The LEDs are connected to the ESP8266 via resistors to limit the current that passes through them and avoid burning them (and burning the digital outputs to which they are connected). The red one will be connected to the 100Ω resistor, the green one to the 82Ω resistor.

The LED has two terminals (called anode and cathode) and, like all diodes, it is a component that has its own polarity: it passes the current when it is forward polarized (i.e. the voltage at the anode is greater than that at the cathode) and it blocks current when it is reverse polarized (i.e. the anode voltage is lower than the cathode voltage). The voltage between the anode and cathode, which we will indicate with Vd, varies according to the color of the light emitted. In particular we have that:

  • Vd = 1.8 V for red LEDs
  • Vd = 1.9 V for yellow LEDs
  • Vd = 2 V for green LEDs
  • Vd = 2 V for orange LEDs
  • Vd = 3 V for blue LEDs
  • Vd = 3 V for white LEDs

How do we identify the anode and cathode of the LED? We do this by looking at its terminals. The longest corresponds to the anode. Also, the LED body has a flattening at one point on the edge indicating that the nearby terminal is the cathode.

So if an LED doesn’t light up it’s possible that it’s wired upside down. In this case, to make it work, simply reverse the connections.

How do you calculate the resistance to connect to the LED?

Please note: this paragraph deals with the calculation of the limiting resistance in a theoretical way and requires a minimum knowledge of the basics of Electrotechnics. Therefore it is not essential for understanding the rest of the project and can be skipped by the reader not interested in such theoretical aspects.

As we have already said, the resistor between the generic GPIO and the LED serves to limit the current flowing through the LED. But how can we calculate its resistance value? Ohm’s Law comes to our aid which says that the potential difference across a resistor (i.e. the voltage measured at the ends of the resistor) is proportional to the current I flowing through it and the constant of proportionality is precisely the resistance value of the resistor R:

V2 - V1 = RI

Please note: for the sake of precision it must be pointed out that while the resistor is the physical component (the actual object), the resistance is its value. So it is improper (even if it happens frequently) to call the resistor with the term resistance.

We can see Ohm’s Law on a simple circuit consisting of a voltage source (the circle on the left) and a resistor:

Representation of Ohm's Law
Representation of Ohm’s Law

The voltage (or potential difference) V2 – V1 impressed by the voltage source on the resistor is equal to the product of R by I.

Now let’s see a slightly more complex scheme where the usual voltage generator, the resistor and a red LED are present:

Circuit for calculating the current limiting resistor on the LED
Circuit for calculating the current limiting resistor on the LED

In our case the Vg represents the voltage present at the digital output of the ESP8266 when it is HIGH and is therefore equal to 3.3V.

Vd is the voltage across the diode (between anode and cathode) when it is forward biased (ie when it is carrying current). Having chosen a red LED, we know from the previous table that Vd = 1.8V.

We need to determine the R-value of the resistor. We still have one unknown: the value of the current I which must flow in the circuit when the pin is in the HIGH state.

Please note: when the digital pin is in the LOW state its voltage (ie Vg) is zero, it follows that also the current I in the circuit is zero.

LEDs generally cannot withstand currents greater than 20mA, so we impose a maximum current of 15mA to be on the safe side.

By Kirchhoff’s voltage law we have that:

Vg - Vr - Vd = 0

From which we derive that:

Vr = Vg - Vd 

Passing to the real values, we have that:

Vr = 3.3V - 1.8V

It follows that:

Vr = 1.5V

But, by Ohm’s Law, we have that:

Vr = RI

from which:

R = Vr / I

Substituting the real values:

R = 1.5V / 0.015A

The result is a value of R equal to 100Ω.

Following a similar reasoning for the green LED, we will have that

R = ((3.3V - 2V) / 0.015A) = 1.3V / 0.015A = 86.67Ω

The closest commercial value is 82Ω. Recalculating the current we will have that this will be equal to approximately 15.8 mA. We are well within the safety limits.

How to create a Telegram bot

Telegram is an instant messaging and VoIP application that can be installed on your smartphone (Android and iPhone) or computer (PC, Mac and Linux). Telegram allows you to create bots that our device can interact with.

Let’s create our bot now!

If you don’t already have Telegram, install it and then look for the botFather bot. Click on the displayed item. The following screen will appear:

First screenshot of the botFather bot
First screenshot of the botFather bot

Type the /start command to read the instructions:

The instructions for creating the bot
The instructions for creating the bot

Now type the command /newbot to create your bot. Give it a name and username:

The creation of the new bot
The creation of the new bot

If your bot was created successfully, you will receive a message with a link to access the bot and the bot token.
Save the bot token as you will need it later for the board to interact with the bot.

This is what the screen where the bot token is written looks like:

The bot's token
The bot’s token

Anyone who knows your bot’s username can interact with it. To filter messages to ignore those that don’t come from your Telegram account, you need to use your Telegram User ID. Thus, when your Telegram bot receives a message, our ESP8266 will know if it comes from us (and therefore process it) or from someone else (and therefore ignore it). But…..how do we find this ID?

In your Telegram account, search for IDBot and start a conversation with that bot:

The first screen of IDBot
The first screen of IDBot

Then type the command /getid and it will reply with your ID:

The result of the /getid command
The result of the /getid command

At this point we have created our bot and we have all the elements to interface it with our device: the username, the token and the userid. We will use this data to insert them into the sketch that we will see later.

The sketch

Let’s create the PlatformIO project

We have already seen the procedure for creating a PlatformIO project in the article How to create a project for NodeMCU ESP8266 with PlatformIO.

Of the libraries indicated, you do not have to install the DHT sensor library for ESPx by Bernd Giesecke but you must install the WiFiManager by tzapu and the UniversalTelegramBot library. Then install the RTClib by Adafruit library as shown in the following photo:

Install the RTClib by Adafruit library
Install the RTClib by Adafruit library

Also install the ArduinoJson library by Benoit Blanchon:

ArduinoJson library by Benoit Blanchon
ArduinoJson library by Benoit Blanchon

the Adafruit BusIO library by Adafruit:

Adafruit BusIO Library by Adafruit
Adafruit BusIO Library by Adafruit

and the Dictionary by Anatoli Arkhipenko library

Library Dictionary by Anatoli Arkhipenko
Library Dictionary by Anatoli Arkhipenko

Now edit the platformio.ini file to add these three lines:

board_build.f_cpu = 160000000L
monitor_speed = 115200
upload_speed = 921600

and these two other lines at the end:

Wire
SPI

so that it looks like this:

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
board_build.f_cpu = 160000000L
monitor_speed = 115200
upload_speed = 921600
lib_deps = 
	arkhipenko/Dictionary@^3.5.0
    adafruit/RTClib@^2.1.3
	adafruit/Adafruit BusIO@^1.15.0
	bblanchon/ArduinoJson@^7.0.2
	tzapu/WiFiManager@^0.16.0
	witnessmenow/UniversalTelegramBot@^1.3.0
    WIRE
	SPI

Once you have downloaded the libraries, you must download another package (zip) of libraries that you find at the link below, unpack them and copy the folders resulting from the decompression into the lib folder of the PlatformIO project:

so that the contents of the lib folder are this:

The PN532 libraries copied to the lib folder of the PlatformIO project
The PN532 libraries copied to the lib folder of the PlatformIO project

Obviously you can download the project from the following link:

The project already contains libraries in the lib folder.

Replace the main.cpp file of the project you created with the one present in the zip file.

Now let’s see how the sketch works.

Initially the necessary libraries are included:

#include <Arduino.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include <WiFiManager.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>   
#include <Dictionary.h>
#include <Wire.h>
#include <RTClib.h> 
#include <SPI.h> 
#include <SD.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>

Here we need to enter the token and Telegram User ID parameters (which in the code is called CHAT_ID) that we got in the previous step when we created the Telegram bot:

// Initialize Telegram BOT
#define BOTtoken "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"  // your Bot Token (Get from Botfather)

// Use @myidbot to find out the chat ID of an individual or a group
// Also note that you need to click "start" on a bot before it can
// message you
#define CHAT_ID "1111111111111111"

#ifdef ESP8266
  X509List cert(TELEGRAM_CERTIFICATE_ROOT);
#endif

WiFiClientSecure client;
UniversalTelegramBot bot(BOTtoken, client);

The variables that determine the timing of reading messages from the bot and reading RFID tags, the ntp server and the parameters for its operation are then defined:

// Checks for new messages every 1 second.
int botRequestDelay = 1000;
unsigned long lastTimeBotRan;

// Checks for new RFID tag every 5 seconds.
unsigned long measureDelay = 5000;                
unsigned long lastTimeRan;

const char* ntpServer = "pool.ntp.org";

const long  gmtOffset_sec = 0;
const int   daylightOffset_sec = 3600;

The pn532_i2c and nfc objects are then defined which manage the RFID reader and the dictUsers dictionary which will be used to associate each user with their privileges. The way it’s set up, it will store a maximum of ten key-value pairs. This value must be calibrated depending on the memory occupation:

PN532_I2C pn532_i2c (Wire) ;
NfcAdapter nfc = NfcAdapter (pn532_i2c);

Dictionary &dictUsers = *(new Dictionary(10));

The file (logFile) that will record the accesses, the chip select for the SD card module, the base path in which the log files will be created, and the GPIOs to which the relay and LEDs will be connected are then defined:

// set up variables using the SD utility library functions:
File logFile;
const int chipSelectSD = D8;
String pathbase = "templog/";
const int relayPin = D3;
const int greenredLed = D4;

The file system will then be organized into folders and files that derive from the current year, month and day. For example, the path templog/24/01/26 indicates the day January 26, 2024 and this file will contain the log of that single day. The creation of folders and files is carried out automatically by the data logger. If a folder is not there, it is created if necessary (for example, if you move from January to February, the data logger will create folder 02 inside folder 24 and files 01, 02, 03…… inside folder 02 , representing the days of February). When the year changes, going from 2024 to 2025, the data logger will create folder 25 at the same level as folder 24 and so on. This way the files are tidy and easily traceable.

We then define the authFile object which will contain the authorizations and its path, followed by some support variables and then the definition of the rtc object which will manage the clock module:

File authFile;
String pathAuth = "auth.txt";

String uuidCode = "";
String temp = "";
String readFile = "";

RTC_DS3231 rtc;

Then follows the definition of the webserver for the REST API which listens on port 80 and the definitions of the Json document and the buffer which will be used to send structured data or receive it to and from the REST API:

// Web server running on port 80
ESP8266WebServer server(80);
StaticJsonDocument<1024> jsonDocument;
char buffer[1024];

The addJsonObject function creates the key-value pairs to insert into the Json (Json document which will then be the response of an API):

void addJsonObject(char *name, float value) {
  JsonObject obj = jsonDocument.createNestedObject();
  obj["name"] = name;
  obj["value"] = value;
}

The splitString function splits a string on the separator and takes the element indicated by the index (we will need it to handle commands coming from the Telegram bot):

// Splits a string on the separator and takes the element indicated by index
String splitString(String data, char separator, int index)
{
  int found = 0;
  int strIndex[] = {
    0, -1  };
  int maxIndex = data.length()-1;

  for(int i=0; i<=maxIndex && found<=index; i++){
    if(data.charAt(i)==separator || i==maxIndex){
      found++;
      strIndex[0] = strIndex[1]+1;
      strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }
  return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}

The handleNewMessages function manages commands coming from the Telegram bot:

void handleNewMessages(int numNewMessages) {
  for (int i=0; i<numNewMessages; i++) {
    // Chat id of the requester
    String chat_id = String(bot.messages[i].chat_id);
    if (chat_id != CHAT_ID){
      bot.sendMessage(chat_id, "Unauthorized user", "");
      continue;
    }
    
    // Print the received message
    String text = bot.messages[i].text;
    Serial.println(text);

    String from_name = bot.messages[i].from_name;

    if (text == "/help") {

      String welcome = "Welcome, " + from_name + ".\n";
      welcome += "Use the following commands to control your outputs:\n\n";
      welcome += "/addmod:uuid:permission adds a user with his privileges (g or d) or modifies the privileges of an already existing user\n";
      welcome += "/del:uuid removes an user\n";
      welcome += "/readuser:uuid returns the privileges of the given user\n";
      bot.sendMessage(chat_id, welcome, "");
    }     
    else if (splitString(text, ':', 0) == "/addmod") {                        
        String uuid = splitString(text, ':', 1);
        String auth = splitString(text, ':', 2);
        if((auth == "g") || (auth == "d")) {
            dictUsers(uuid, auth);
            SD.remove(pathAuth);
            int dictEntriesNumber = dictUsers.count();
            logFile = SD.open(pathAuth, FILE_WRITE);

            // if the file opened okay, write to it:
            if (logFile) {
              for(int i = 0; i < dictEntriesNumber; i++) {            
                  String key = dictUsers.key(i);
                  String keyTrim = key;
                  keyTrim.trim(); 
                  String value = dictUsers[key];
                  value.trim();
                  if(keyTrim != "") {
                    String log = key + "," + value;
                    logFile.println(log);
                  }
                  
              }    
              // close the file:
              logFile.close();
            } 
            bot.sendMessage(chat_id, "Added/modified user " + uuid + " with permission " + auth);
        } else {
          bot.sendMessage(chat_id, "Wrong auth parameter. Must be g or d");
        }
    } 
    else if (splitString(text, ':', 0) == "/del") {                        
          String uuid = splitString(text, ':', 1);
          dictUsers.remove(uuid);
          SD.remove(pathAuth);
          int dictEntriesNumber = dictUsers.count();
          logFile = SD.open(pathAuth, FILE_WRITE);

          // if the file opened okay, write to it:
          if (logFile) {
            for(int i = 0; i < dictEntriesNumber; i++) {              
                String key = dictUsers.key(i);
                String keyTrim = key;
                keyTrim.trim(); 
                String value = dictUsers[key];
                value.trim();
                if(keyTrim != "") {
                  String log = key + "," + value;
                  logFile.println(log);
                }                
            }    
            // close the file:
            logFile.close();
          } 
        bot.sendMessage(chat_id, "Removed " + uuid + " user");
    } 
    else if (splitString(text, ':', 0) == "/readuser") {                        
        String uuid = splitString(text, ':', 1);
        String auth = dictUsers[uuid];
        bot.sendMessage(chat_id, uuid + " " + auth);
    } 
 else {
       bot.sendMessage(chat_id, "Unrecognized message. Please retry...", "");
    }

    
  }
}

If the /help command is typed, the available commands are shown (already examined in the paragraph illustrating the set of commands available on Telegram).

The getDate function creates a Json containing the current date and time in order to check the accuracy of the RTC form. The Json document it creates is used as a response for the getDate API:

void getDate() {

  DateTime now = rtc.now(); // Read the date and time from the DS3231    

  jsonDocument.clear(); // Clear json buffer
  addJsonObject("year", now.year());
  addJsonObject("month", now.month());
  addJsonObject("day", now.day());
  addJsonObject("hour", now.hour());
  addJsonObject("minutes", now.minute());
  addJsonObject("seconds", now.second());
  serializeJson(jsonDocument, buffer);
  server.send(200, "application/json", buffer);
}

The sanitize function ensures that the values ​​of months, days, hours, minutes and seconds always have two digits, prepending a zero in the case of single-digit numbers. So “1” becomes “01” and so on:

String sanitize(String hms) {
  if(hms.length() < 2) {
    hms = "0" + hms;
  }
  return hms;
}

The fileIsPresent function checks whether or not a given file whose path is provided is present in the file system:

bool fileIsPresent(String path) {
  return SD.exists(path);
}

It is used for example by the fileExists API.

The delUser function is used to remove a given user by first deleting it from the dictionary and then updating the auth.txt file. This function responds to the delUser API:

void delUser() {
  Serial.println("delUser API");
  if (server.hasArg("plain") == false) {
  //handle error here
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);
  String uuid = jsonDocument["uuid"];

  dictUsers.remove(uuid);

  SD.remove(pathAuth);
  int dictEntriesNumber = dictUsers.count();
  logFile = SD.open(pathAuth, FILE_WRITE);

  // if the file opened okay, write to it:
   if (logFile) {
    for(int i = 0; i < dictEntriesNumber; i++) {
      
        String key = dictUsers.key(i);
        String keyTrim = key;
        keyTrim.trim(); 
        String value = dictUsers[key];
        value.trim();
        if(keyTrim != "") {
          String log = key + "," + value;
          logFile.println(log);
        }
        
    }    
    // close the file:
    logFile.close();
  } 
  // Respond to the client
  server.send(200, "application/json", "{}");
}

The addModUser function adds a user with its privileges or modifies the privileges of an already existing user. Like the previous function, it updates the dictionary first and then the auth.txt file. This function responds to the addModUser API:

void addModUser() {
  Serial.println("addModUser API");
  if (server.hasArg("plain") == false) {
  //handle error here
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);


  String uuid = jsonDocument["uuid"];
  String auth = jsonDocument["auth"];

  dictUsers(uuid, auth);
  SD.remove(pathAuth);
  int dictEntriesNumber = dictUsers.count();
  logFile = SD.open(pathAuth, FILE_WRITE);

  // if the file opened okay, write to it:
   if (logFile) {
    for(int i = 0; i < dictEntriesNumber; i++) {
      
        String key = dictUsers.key(i);
        String keyTrim = key;
        keyTrim.trim(); 
        String value = dictUsers[key];
        value.trim();
        if(keyTrim != "") {
          String log = key + "," + value;
          logFile.println(log);
        }
        
    }    
    // close the file:
    logFile.close();
  }  

  // Respond to the client
  server.send(200, "application/json", "{}");
}

The setDate function takes as input a Json containing year, month, day, hours, minutes and seconds and adjusts the clock with this data. This is in case we want to adjust the clock manually. This function is called by the setDate API:

void setDate() {
  Serial.println("setDate API");
  if (server.hasArg("plain") == false) {
  //handle error here
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);


  String year_s = jsonDocument["year"];
  String month_s = jsonDocument["month"];
  String day_s = jsonDocument["day"];
  String hour_s = jsonDocument["hour"];
  String minutes_s = jsonDocument["minutes"];
  String seconds_s = jsonDocument["seconds"];
  rtc.adjust(DateTime(year_s.toInt(), month_s.toInt(), day_s.toInt(), hour_s.toInt(), minutes_s.toInt(), seconds_s.toInt()));

  // Respond to the client
  server.send(200, "application/json", "{}");
}

The fileExists function checks the existence of a given file starting from the path created with the year, month and day. It uses the fileIsPresent function encountered previously and is called by the fileExists API:

void fileExists() {
  Serial.println("fileExists API");
  if (server.hasArg("plain") == false) {
  //handle error here
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);

  String year = jsonDocument["year"];
  String month = jsonDocument["month"];
  String day = jsonDocument["day"];

  String path = "templog/" + year + "/" + month + "/" + day;
  String res = "";
  if(fileIsPresent(path)) {
    res = "true";
  } else {
    res = "false";
  }

  // Respond to the client
  String response = "{\"isPresent\" : " + res + "}";
  server.send(200, "application/json", response);
}

The readfile function reads the file present at the given path and stores it in the readFile variable. It is called by the fileRead API:

void readfile(String path) {
  readFile = "";
  char patharray[path.length() + 1];
  path.toCharArray(patharray, path.length() + 1);
  //SD.begin(chipSelectSD);
  logFile = SD.open(patharray);
  if (logFile) {
    // read from the file until there's nothing else in it:
    for (unsigned long i = 0; i <=  logFile.size() - 1; i++) {
      logFile.seek(i);
      readFile = readFile + (char)logFile.peek();
    }
    // close the file:  
    logFile.close();  
  }  
}

The fileRead function is the actual API that returns the contents of the file read by the readfile function:

void fileRead() {
  Serial.println("fileRead API");
  if (server.hasArg("plain") == false) {
  //handle error here
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);

  String year = jsonDocument["year"];
  String month = jsonDocument["month"];
  String day = jsonDocument["day"];

  String path = "templog/" + year + "/" + month + "/" + day;
  if(fileIsPresent(path)) {
    readfile(path);
    String response = "";
    readFile.trim();
    response +=   readFile;
    server.send(200, "application/json", response); 
  } else {
    server.send(404, "application/json", "{}");
  }

}

The readAuth function reads the auth.txt file. This function responds to the readAuth API:

void readAuth() {
  Serial.println("readAuth API");
  if (server.hasArg("plain") == false) {
  //handle error here
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);
  
  String path = pathAuth;
  String res = "";
  if(fileIsPresent(path)) {    
    readfile(path);
    String response = "";
    readFile.trim();
    response +=   readFile;
    //response += "\"}";    
    server.send(200, "application/json", response); 
  } else {
    server.send(404, "application/json", "{}");
  }

}

The setupApi function associates the 7 APIs /getDate, /readAuth, /fileExists, /fileRead, /setDate, /addModUser, /delUser with the respective getDate, readAuth, fileExists, fileRead, setDate, addModUser and delUser functions:

void setupApi() {
  server.on("/getDate", getDate);   // gets current date and hour
  server.on("/readAuth", readAuth);   // reads the permissions file
  server.on("/fileExists", HTTP_POST, fileExists); // check if the file exists
  server.on("/fileRead", HTTP_POST, fileRead); // reads the file
  server.on("/setDate", HTTP_POST, setDate);   // sets date and hour
  server.on("/addModUser", HTTP_POST, addModUser); // adds a user with his privileges or modifies the privileges of an already existing user
  server.on("/delUser", HTTP_POST, delUser); //  deletes a user with his privileges

  // start server
  server.begin();
}

The updatefile function creates, if not present, the file with the correct path obtained from the current date and writes the UUID of the user who tried to log in, concatenating it with the current hour, minutes and seconds and that user’s privileges. user (“g” or “d”) . If the file is already present, it simply writes the new data (append) to it:

void updatefile(DateTime now) {
  String path_folder = pathbase + sanitize(((String)now.year()).substring(2,5)) + "/" + sanitize((String)now.month()) + "/";
  String path_file = path_folder + sanitize((String)now.day()); 
  String auth = dictUsers[uuidCode];
  auth.trim();
  if(auth == "") {
    auth = "d";
  }
  String line = uuidCode + "," + sanitize((String)now.hour()) + ":" + sanitize((String)now.minute()) + ":" + sanitize((String)now.second()) + "," + auth;
  SD.begin(chipSelectSD);
  char path_folder_array[path_folder.length() + 1];
  path_folder.toCharArray(path_folder_array, path_folder.length() + 1);
  char path_file_array[path_file.length() + 1];
  path_file.toCharArray(path_file_array, path_file.length() + 1);
  
  if(!SD.exists(path_folder_array)) {SD.mkdir(path_folder_array);}  

  logFile = SD.open(path_file_array, FILE_WRITE);


  // if the file opened okay, write to it:
  if (logFile) {
    logFile.println(line);
    // close the file:
    logFile.close();
  } 
}

The setup function begins with the initialization of the serial port, the initialization of the Wire module (with the setting of its clock at the maximum speed allowed), the initialization of the RTC and SPI modules:

Serial.begin (115200);
delay(2000);

Wire.setClock(400000);  // https://www.arduino.cc/reference/en/language/functions/communication/wire/setclock/
Wire.begin();
rtc.begin(); 
SPI.begin();   

The initialization of the SD card module then follows:

// Initialize SPI communication with the SD module
if (!SD.begin(chipSelectSD)) {
Serial.println("Error initializing the SD card.");
return;
} 
Serial.println("SD card initialized successfully.");

and reading the auth.txt file, the contents of which will be loaded into the dictionary:

  authFile = SD.open("auth.txt");
  if (authFile) {
  Serial.println("Reading auth file");

  // Reads the auth.txt file and populates the access permission dictionary
  while (authFile.available()) {
      char ch = authFile.read(); // read characters one by one from Micro SD Card
      if(ch == '\n') {
        dictUsers(splitString(temp, ',', 0), splitString(temp, ',', 1));
        temp = "";
      } else {
      temp += ch;
      }
    }
    authFile.close();
    } else {
      Serial.println("Error reading auth file");
    }

If the clock is not set, it is set by the time sent by the computer when loading the sketch. The RFID reader is then initialized:

// Set the date and time only once if the DS3231 has not been previously initialized
if (rtc.lostPower()) {
Serial.println("RTC not initialized. I set the time...");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
Serial.println ("NDEF Reader");

nfc.begin ();

Then follows the management part of the WiFi connection:

WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
// it is a good practice to make sure your code sets wifi mode how you want it.

//WiFiManager, Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wm;

// reset settings - wipe stored credentials for testing
// these are stored by the esp library
// wm.resetSettings();

// Automatically connect using saved credentials,
// if connection fails, it starts an access point with the specified name ( "AutoConnectAP"),
// if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect())
// then goes into a blocking loop awaiting configuration and will return success result

bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("AutoConnectAP","password"); // password protected ap

if(!res) {
    Serial.println("Failed to connect");
    ESP.restart();
} 
else {
    //if you get here you have connected to the WiFi    
    Serial.println("Connected...yeey :)");
}

The setupApi() function is then called which serves to route the various REST requests to the appropriate functions (as already seen above).

The pin that controls the relay is initialized as OUTPUT and initialized to the HIGH value:

  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);

The UTC time is then received from the NTP server, the certificates for the operation of the Telegram bot are added and the initial messages are sent to the bot:

#ifdef ESP8266
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);      // get UTC time via NTP
client.setTrustAnchors(&cert); // Add root certificate for api.telegram.org
#endif

bot.sendMessage(CHAT_ID, "Hi! I'm online!", "");
bot.sendMessage(CHAT_ID, "Ready to operate. Type /help to see the command list.", "");

Finally, the pin that controls the LEDs is initialized as OUTPUT and initialized to the LOW value so that only the red LED lights up:

pinMode(greenredLed, OUTPUT);
digitalWrite(greenredLed, LOW);

The loop function begins with the call to the REST API handler:

server.handleClient();

It continues with a block that each botRequestDelay ms checks if commands have arrived from the Telegram bot and sends them, to manage them, to the handleNewMessages function, which we have already encountered:

if (millis() > lastTimeBotRan + botRequestDelay)  {
        int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
        while(numNewMessages) {
          Serial.println("got response");
          handleNewMessages(numNewMessages);
          numNewMessages = bot.getUpdates(bot.last_message_received + 1);
        } 
        lastTimeBotRan = millis(); 
    }  

Finally we find the block that every measureDelay ms detects the presence of an RFID tag in the vicinity of the reader, reads its UUID and obtains its permissions from the dictionary. In case of “granted” access, it turns on the green LED, triggers the relay, sends a warning message to the bot, waits 5 seconds, turns off the green LED and turns on the red one, triggers the relay again in the rest position. In case of “denied” access, leave the red LED on and warn of the access attempt by sending a message to the bot:

if (millis() > lastTimeRan + measureDelay)  {
          if (nfc.tagPresent (100))
          {
              DateTime now = rtc.now();
              NfcTag tag = nfc.read ();
              uuidCode = tag.getUidString();
              uuidCode.replace(" ", "");
              updatefile(now);          
              String auth = dictUsers[uuidCode];
              Serial.println("UUID: " + uuidCode);
              Serial.println("auth: " + auth);
              auth.trim();
              if(auth == "g") {
                digitalWrite(greenredLed, HIGH);
                Serial.println("access granted");
                Serial.println("opening the door");
                bot.sendMessage(CHAT_ID, "Granted access to user " + uuidCode + " at " +  sanitize((String)now.day()) + "/" + sanitize((String)now.month()) + "/" + sanitize((String)now.year())   +  " "  + sanitize((String)now.hour()) + ":" + sanitize((String)now.minute()) + ":" + sanitize((String)now.second()));
                digitalWrite(relayPin, LOW);
                delay(5000);
                digitalWrite(relayPin, HIGH);
                Serial.println("closing the door");
                digitalWrite(greenredLed, LOW);
              } else {
                digitalWrite(greenredLed, LOW);
                bot.sendMessage(CHAT_ID, "Denied access to user " + uuidCode + " at " +  sanitize((String)now.day()) + "/" + sanitize((String)now.month()) + "/" + sanitize((String)now.year())   +  " "  + sanitize((String)now.hour()) + ":" + sanitize((String)now.minute()) + ":" + sanitize((String)now.second()));
                Serial.println("access denied");
              }
              uuidCode = "";    
          }         
    lastTimeRan = millis(); 
  }  

How to connect the board to the Internet

After uploading the sketch to the board, open the Serial Monitor to see the messages coming from the device.

IMPORTANT NOTE: after loading the sketch on the ESP8266 it may happen that the RFID sensor is not recognized. This causes the ESP8266 to crash and restart in an infinite loop. To remedy this, simply disconnect the RFID reader’s power cord and reconnect it. In this way, the next time the ESP8266 is restarted, this reader will be recognized and our microcontroller will continue with its normal setup.

First the board goes into Access Point mode and will provide us with an IP address that we will use shortly. This operation is used to connect the board to the Internet without having to enter the WiFi network parameters (SSID and password) in the code.

The board provides us with its IP address
The board provides us with its IP address

In this case the IP address is 192.168.4.1.

At this point the ESP8266 is in Access Point mode (with AutoConnectAP SSID) and we need to connect our computer to the AutoConnectAP network. If we go to the networks menu of our computer, we should also see the AutoConnectAP network in the list of wireless networks.

List of available WiFi networks
List of available WiFi networks

Connect your computer to the AutoConnectAP network. Then go to your browser and enter the IP previously provided by the ESP8266 (which in this example is 192.168.4.1)

You will see a screen like this:

The browser screen for choosing the network
The browser screen for choosing the network

Click the ConfigureWiFi button. It will show you the available networks:

List of available networks
List of available networks

Choose your network’s SSID:

Choose your network
Choose your network

Enter your network password and click the save button:

Enter your password
Enter your password

The board's response
The board’s response

The ESP8266 module keeps the access parameters stored even if you turn it off, it will remember them when restarting and will automatically reconnect without having to repeat this procedure. Only if you reset it by uncommenting this line

// wm.resetSettings();

will lose the connection parameters.

Among the various messages that will be printed on the Serial Monitor, the WiFiManager library will also tell us which IP has been assigned to it by the WiFi modem.

Please note: the device can only memorize one network. If you later connect it to another network, it will forget the settings of the previous network.

Let’s test the project with REST APIs

Once the ESP8266 has been connected to the WiFi network it will provide us with its IP address via the PlatformIO Serial Monitor, as visible in the following figure:

We obtain the IP of the board
We obtain the IP of the board

In this case the IP assigned by the WiFi router to the board is 192.168.1.153. This IP will be used to compose the REST API.

PLEASE NOTE: obviously it is not certain that in your case the same IP address will be assigned to your board but it will certainly be different from mine. In experiments with Postman you will, obviously, have to use the IP assigned to your board.

To interact with the board we need special software called Postman. After installing the program, we are ready to use it.

This is what its home screen looks like:

Postman home screen
Postman home screen

In the main window there is a bar where you will need to enter the API.

To the left of this bar there is a drop-down menu that allows you to choose the type of API (for example GET, POST, PUT…).

In general the structure of the API is of this form:

http://IP_ESP8266/APIname

Let’s try the GET APIs. In the drop-down menu on the left we choose the GET item. Let’s try the getDate API by placing the line

192.168.1.153/getDate

in the box to the right of the GET menu and press the Send button. Hopefully we’ll get a response like this:

getDate API
getDate API

Now let’s try readAuth. In the drop-down menu on the left we choose the GET item. Let’s test the readAuth API by placing the line

192.168.1.153/readAuth

in the box to the right of the GET menu and press the Send button. Hopefully we’ll get a response like this:

readAuth API
readAuth API

Let’s now move on to the POST type API. In the drop-down menu on the left we choose the POST type.

They involve providing a Json document as input data to the API. To provide this Json, you will need to select the Body item below the URL bar. Then select the raw item (under Body) and then, on the drop-down menu on the right, select the JSON item instead of Text.

Let’s see it with the first POST API, fileExists. Let’s put the line

192.168.1.153/fileExists

we insert the Json file as explained above and press the Send button.

The answer should be something like this:

fileExists API
fileExists API

To adjust the clock we use the setDate always inserting the Json as explained above. Using the line

192.168.1.153/setDate

we should therefore have a situation similar to this:

setDate API
setDate API

By pressing the Send button the clock will be set with the incoming Json data.

Now let’s read a given file with the command

192.168.1.153/fileRead

always inserting the Json containing the year, month and day to identify it in the file system.

The situation will be like this:

fileRead API
fileRead API

Now let’s test the addModUser API. The command will be;

192.168.1.153/addModUser
addModUser API
addModUser API

In this case, if the user 73051E99 is not already present in the auth.txt file he will be added with his privilege (d), if he is already present his privilege will simply be changed.

Let’s see the last API i.e. delUser, always of type POST.

In this case the command will be

192.168.1.153/delUser
delUser API
delUser API

By pressing the Send button, user 73051E99 will be deleted (both from the dictionary and from the auth.txt file).

The next video shows how some REST APIs work with Postman (here the board was connected to a different router which assigned it the address 192.168.1.121):

Let’s test the project with Telegram

The next video shows how some commands work with Telegram:

Final remarks

The sketch is quite complex and includes many libraries. So it’s definitely a bit heavy to run on a board like that. Let’s add the fact that the RFID reader is not particularly fast in executing its operations (in particular the nfc.tagPresent() command significantly slows down the loop function as it is blocking). This command includes a timeout (for example nfc.tagPresent (100)) which ensures that the function does not block the execution of the sketch for too long. Furthermore, the bot.getUpdates(bot.last_message_received + 1) command is also quite slow. The result of all this is a certain slowness in the execution of REST APIs and commands on the Telegram bot. You have to wait several seconds before they are completed. You can find a compromise between the parameters botRequestDelay, measureDelay and the timeout of nfc.tagPresent() in order to have the most responsive behavior possible.

Newsletter

If you want to be informed about the release of new articles, subscribe to the newsletter. Before subscribing to the newsletter read the page Privacy Policy (UE)

If you want to unsubscribe from the newsletter, click on the link that you will find in the newsletter email.

Enter your name
Enter your email
0 0 votes
Article Rating
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
Scroll to Top