RFID gate access control system. Advanced integration with Raspberry Pi and Telegram

Introduction

RFID gate access control system: an advanced system for access and permission management supported by the power of the Raspberry Pi. This integrated solution is designed to offer efficient and flexible access control, using RFID technology to ensure secure and rapid identification of users.

At the heart of this ecosystem lies a SQLite3 database, an ideal option for storing and managing user data and permissions. SQLite3 offers a unique combination of lightweight, reliability and flexibility, enabling rapid data access and optimized information management without the need for a separate server.

In addition to data management, the system also includes a REST API server built using Flask that exposes CRUD functionality for user table management. CRUD stands for Create, Read, Update and Delete, i.e. the main operations that are generally performed on a database record. This allows administrators to dynamically change user permissions. Apart from this, REST APIs allow you to view the access log for a given day. The REST API interface provides an intuitive and scalable experience for interacting with the system, allowing for easy integration with other applications and systems.

Furthermore, the system is enriched by a dedicated Telegram bot, which allows users to receive instant notifications on accesses via direct messages and to execute CRUD commands on the user table (such as those available with REST APIs) directly from the messaging platform. This adds a level of flexibility and accessibility to the system, allowing users to manage access wherever they are.

From a technical point of view, the entire system is virtualized via Virtualenv (which we’ll talk about later), ensuring an isolated and manageable environment for running Python applications on the Raspberry Pi. This approach allows for greater system security and stability, ensuring a reliable and consistent experience for users.

In summary, RFID access control represents a complete and cutting-edge solution for access and permission management, integrating advanced technologies such as RFID, SQLite3, REST API and Telegram bot to offer a complete and highly customizable experience to end users.

Description of how RFID gate access control system works

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 a table called users in the database which contains, for each row, the UUID of a user, his name and his access privileges (“g” for granted and “d” for denied). Every time an RFID tag is detected by the reader, the system reads this table and loads it into a dictionary (which we will talk about in one of the following paragraphs). Therefore the dictionary is always updated because the table may have been modified in the meantime (for example, a user’s access privileges may have changed). Furthermore, the system has a small buzzer (which will be briefly described later) which will emit an acoustic signal every time the RFID reader has detected an RFID tag (i.e. a badge or key fob equipped with an RFID tag).
  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 (which has just been updated by reading the database users table) 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 in the database in the accessusers table. This provides a time log of access, useful for monitoring and security. For each row, the UUID, date, time, and whether entry was granted (“g”) or denied (“d”) is recorded. If the UUID was not present in the users table, the access attempt would still be recorded in the database but with “n” privilege (which stands for not recognized) and entry would be denied (as the UUID is unknown to the system).
  2. Telegram notifications: At the same time as the login attempt (which may or may not be successful), the system sends a warning message to the Telegram bot. This allows users to receive real-time notifications about access, increasing control and security.
  3. 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 read the authorization table, add a new user with its privileges, modify the privileges of an existing user or delete a user but also read the accesses that occurred on a given day. 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 database.

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 Raspberry Pi:
    • The Raspberry Pi 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, distributed by the AZ-DELIVERY company, consists of an RFID reader, a badge, a key ring (always RFID), two types of connectors to be soldered onto the reader (one with straight contacts and one with right-angled contacts ). 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 73051e99f1 while the key ring has hexadecimal code a305cd0b60.

The RFID reading module interfaces with the Raspberry PI via SPI communication which we will talk about in the next paragraph.

In the next photo you can see the complete kit:

Complete RFID kit
Complete RFID kit

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.

The buzzer

The buzzer is an electronic component widely used to generate acoustic signals. There are two main types of buzzers, active and passive:

  • Passive buzzers: they work similarly to speakers: they require an input signal, usually in the form of a PWM square wave, to produce sound. This signal can be varied in amplitude and frequency to obtain different tones, allowing you to play melodies and generate more complex sound signals than active buzzers.
  • Active buzzers : they are equipped with an internal integrated circuit that generates a predefined frequency when powered, without the need to provide an external input signal. Basically, you just apply power to make them play at the preset frequency.

The buzzer used in this project is an active type.

What is Flask

Flask is a lightweight framework for building web applications in Python. It is flexible, easy to use, and offers a wide range of features for developing RESTful API servers.

Essentially, Flask allows you to define “routes” or URL paths that correspond to client requests. Each route is associated with a Python function that processes the request and returns a response to the client. This approach makes it easy to create endpoints for REST APIs, where HTTP requests (such as GET, POST, PUT, DELETE) are handled appropriately to access and manipulate server resources.

Flask is based on Werkzeug, a WSGI toolkit, and Jinja2, a templating engine for Python. It uses a “micro” approach, which means it provides only the bare minimum to create web applications, leaving the programmer the freedom to extend the functionality as needed.

Due to its simplicity and flexibility, Flask is widely used for building REST API servers, providing an efficient and scalable way to manage communications between clients and servers in web applications and microservices-based services. More in particular:

  1. Lightweight framework: Flask is designed to be lightweight and non-intrusive. It does not impose a rigid structure for your applications, but only provides the essential tools for building web servers.
  2. Flexible routing: With Flask, you can easily define URL paths, known as “routes”, that correspond to client requests. Each route is associated with a Python function, called “view function”, which processes the request and returns a response.
  3. HTTP request management: Flask offers an intuitive interface to handle various HTTP operations such as GET, POST, PUT, DELETE and others. This allows you to easily create endpoints for REST APIs, where HTTP requests are processed appropriately to access and manipulate server resources.
  4. Modular extensions: Flask supports an extension system that allows you to add additional functionality to your application in a modular way. There are extensions available for almost every need, from authentication to database access and more.
  5. Integration with Werkzeug and Jinja2: Flask relies on Werkzeug, a WSGI toolkit for Python, to handle HTTP requests and responses. It also uses Jinja2 as a templating engine to dynamically generate the HTML contents of web pages.
  6. “Micro” approach: Flask takes a “micro” approach, meaning it provides only the bare minimum for building web applications. This allows programmers to maintain complete control over the application’s functionality and extend it as needed.
  7. Simplicity and flexibility: Due to its simplicity and flexibility, Flask is widely used for building REST API servers. It is particularly suitable for small to medium-sized projects, where it is important to keep the code clean, readable and easily maintainable.

In summary, Flask offers an efficient and scalable way to create web servers and manage communications between clients and servers in web applications and microservices-based services. Its flexibility and ease of use make it a popular choice among Python programmers for developing web applications.

What is Sqlite3

SQLite is a lightweight, fast, self-contained database designed to manage local databases easily and efficiently. It is a software library that provides a relational database management system (RDBMS) embedded directly into applications. Unlike traditional client-server systems, SQLite works without the need for a separate server, making it ideal for embedded, mobile and web applications.

With SQLite, you can create, manage, and query databases directly from your code, without having to install or configure a separate database server. Due to its portability and lightweight, SQLite is widely used in a wide range of applications, including mobile applications, web browsers, embedded systems, desktop applications and more.

One of SQLite’s distinctive features is its ability to manage small to medium-sized databases with extreme efficiency and speed. Even though it is lightweight, SQLite supports many of the advanced features of relational databases, including ACID transactions, referential integrity constraints, triggers, views, and much more.

  1. Embedded databases: SQLite3 is an embedded relational database that operates directly on database files without the need for a separate client server. This makes it ideal for integration into software applications that require data management without installing and configuring a separate database system.
  2. Zero configuration: SQLite3 requires no server configuration or administration. Just create a database file and SQLite3 is ready to use. This makes it extremely easy to use and suitable for projects where the complexity and overhead of a traditional database server is not necessary.
  3. Full SQL support: SQLite3 supports most of the standard SQL language, allowing you to easily create, modify and query your database using familiar SQL statements such as SELECT, INSERT, UPDATE and DELETE. It also offers advanced features like JOIN, GROUP BY, and ORDER BY to query and analyze data effectively.
  4. ACID transactions: SQLite3 supports ACID (Atomicity, Consistency, Isolation, Durability) transactions, ensuring that write operations to the database are safe and reliable. Transactions can be initiated, executed, and committed or rolled back as needed, ensuring data integrity and consistency.
  5. Portability: SQLite3 is highly portable and can run on a wide range of platforms, including desktop operating systems (Windows, macOS, Linux) and embedded devices such as smartphones, tablets, and IoT devices. This portability makes it a popular choice for cross-platform application development.
  6. Small size: SQLite3 is designed to be extremely lightweight and has a small binary file size, taking up few system resources and requiring a minimal amount of memory to run. This makes it suitable for use on resource-limited devices, such as smartphones and IoT devices.
  7. Ease of use: SQLite3 is designed to be simple to use and requires only a few lines of code to start using the database in an application. It also offers extensive documentation and a large community of developers who provide support and learning resources.

In summary, SQLite3 is an excellent choice for applications that require a lightweight, built-in, easy-to-use database. It offers a wide range of SQL functionality, transaction security and reliability, and portability that makes it suitable for a wide range of projects and platforms.

What is a dictionary

  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 Raspberry Pi:
    • In programming contexts for the Raspberry Pi, 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.

What is Virtualenv

Virtualenv is a tool that allows developers to create and manage isolated and independent Python environments within their projects. With virtualenv, you can create an isolated Python environment that contains only your project-specific dependencies, without interfering with other Python installations on your system.

Using virtualenv offers numerous benefits, including the ability to maintain order and consistency across project dependencies, the ease of sharing and distributing code, and the ability to test applications in a controlled, isolated environment. Additionally, virtualenv allows developers to experiment with different versions of Python and libraries without affecting other applications or the operating system.

Once you’ve created a virtual environment with virtualenv, you can enable it to use specific Python versions and project dependencies. This allows developers to work efficiently and securely on complex projects without worrying about dependency conflicts or compatibility issues.

Now, we will explore the features and usage of virtualenv in more detail through a series of points:

  1. Creating a virtual environment: With virtualenv, you can create a virtual environment in just a few steps, specifying the destination directory path and possibly the Python version to use.
  2. Enabling and disabling the virtual environment: After creating a virtual environment, you can activate it using a specific script generated by virtualenv. Once activated, the virtual environment changes environment variables to point to its directory, isolating the project from other Python environments installed on the system. When you finish working on your project, you can deactivate the virtual environment to return to the default Python environment.
  3. Project dependency management: By using a virtual environment, you can install project dependencies in isolation, without affecting other Python installations on your system. This helps maintain order and consistency between project dependencies and simplifies library version management.
  4. Code sharing and distribution: Virtual environments can be easily shared and distributed along with the project’s source code. This allows other team members to easily reproduce the development environment and ensure consistency across different installations.
  5. Secure testing and development: Using virtual environments allows developers to test their applications in a controlled, isolated environment, minimizing the risk of dependency conflicts or compatibility issues. It also allows you to experiment with different configurations and versions of Python without affecting your operating system or other applications.

The set of REST APIs available

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

  • readusers (GET) returns the contents of the users table containing the permissions for each user;
  • insertuser (POST) adds a user with his UUID, name and access permissions to the users table via a Json of the type:
                    {
                          "uuidUser": "73051e99f1",
                          "name" : "Mike",
                          "access" : "d"
                     }
  • moduser (POST) modifies an existing user in the users table with his UUID, name and access permissions via a Json of the type:
                     {
                          "uuidUser": "73051e99f1",
                          "name" : "Mike",
                          "access" : "g"
                      }
  • deluser (POST) delete a user by their UUID with a Json like:
                      {
                             "uuidUser": "1105cd0b60"
                       }
  • getdata (POST) returns the list of accesses recorded in the database corresponding to a given day identified via a Json of the type:
                      {
                           "year": "2024",
                           "month" : "3",
                           "day" : "17"
                       }

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 database via the following commands:

  • /adduser uuid name permission adds a user with his privileges (g or d) (example: /adduser a305cd0b60 Paul d)
  • /moduser uuid name permission modifies the data of an already existing user (example: /moduser a305cd0b60 Ringo g)
  • /del uuid removes a user identified by his uuid (example: /del a305cd0b60)
  • /readuser uuid returns the data of the specified user (example /readuser a305cd0b60)

What components do we need for our RFID gate access control system?

The list of components is not particularly long:

  • a breadboard to connect the Raspberry PI to other components
  • some DuPont wires (male – male, male – female, female – female)
  • an active KY-012 type buzzer
  • a 100Ω resistor
  • an 82Ω resistor
  • a green LED
  • a red LED
  • an RFID kit like the one shown in the previous paragraph
  • a module with double/single opto-isolated relay
  • a (micro) SD card of no more than 32GB formatted in FAT32
  • a possible USB WiFi dongle for the Raspberry
  • and, of course, a Raspberry!

The SD cards I used for the experiments are one 8GB and the other 16GB.

The project has been successfully tested on a Raspberry Pi 1 Model B and on a Raspberry PI 3 Model B but it is not excluded that it will also work on other Raspberry models.

Project implementation

The electrical diagram

Before creating the actual circuit let’s take a look at the pinouts of the two Raspberries used:

Pinout of the Raspberry Pi 1 Model B (left) and Raspberry Pi 3 Model B (right)
Pinout of the Raspberry Pi 1 Model B (left) and Raspberry Pi 3 Model B (right)

As you can see, the two pinouts coincide for the first 13 rows.

Below we can see the pinout of the RFID module:

RFID module pinout
RFID module pinout

The IRQ terminal will not be used.

And here is the buzzer pinout:

Active buzzer pinout
Active buzzer pinout

We will use the Ground – GND and Output signal -S terminals.

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.

An example of a module with double opto-isolated relay
An example of a module with double opto-isolated relay

Let’s now see the electrical diagram of the project, created as usual with Fritzing, in two versions. One for the Raspberry Pi 1 Model B and the other for the Raspberry Pi 3 Model B:

Raspberry Pi 1 Model B rfid gate access control system wiring diagram
Raspberry Pi 1 Model B rfid gate access control system wiring diagram

Raspberry Pi 3 Model B rfid gate access control system wiring diagram
Raspberry Pi 3 Model B rfid gate access control system wiring diagram

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.

The RFID module is connected to the SPI port and the 3.3V power supply of the Raspberry according to the table:

RFIDGPIO RASPBERRY
3.3V3.3V
RST25
GNDGND
MISO9
MOSI10
CK11
SDA (which would be Chip Select)8

The control input of the IN1 relay module is connected to the Raspberry’s GPIO 17 (even though the module has two relays, we will only use one) while the two 5V and GND pins of the Raspberry are used to power the dual relay module, connecting the 5V pin (Raspberry side) to the VCC pin (dual relay module side) and the two GND pins (ground).

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 buzzer is connected to GPIO 22.

The LEDs are connected to the Raspberry to GPIO 18 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 Raspberry 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 Raspberry 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.

Preparing the Raspberry

In order to install the application on the Raspberry it is necessary to take some preliminary steps and install some software.

Let’s start immediately with the installation of the operating system.

The operating system chosen is a distribution made specifically to run on all types of Raspberry, even the older ones. The tests were done on a Raspberry Pi 1 Model B and a Raspberry PI 3 Model B.

If the Raspberry does not have a native wireless connection you can use a WiFi dongle to insert into one of its USB sockets.

Let’s download and install the operating system on the SD card

To download the latest version, go to https://www.raspberrypi.com/software/operating-systems/

and go to the Raspberry Pi OS (Legacy) section. You will download a version that has no graphical environment so that it is as lightweight as possible:

The chosen distribution
The chosen distribution

The downloaded file will be compressed in xz format. To unzip it on Linux you will first need to install the tool:

sudo dnf install xz           on CentOS/RHEL/Fedora Linux.
sudo apt install xz-utils     on Ubuntu/Debian

and then give the command line:

xz -d -v filename.xz

where filename.xz is the name of the file you just downloaded containing the operating system.

On Windows it will be sufficient to use one of these tools: 7-Zip, winRAR, WinZip.

The result will be a file with img extension which is the image to flash on the Raspberry SD card.

To flash the image on the SD card you will use the Balena Etcher tool which works on both Linux, Windows and MACOS.

Its use is very simple: simply select the image to flash, the destination SD card and press the Flash button.

This is what its interface looks like:

The interface of the Balena Etcher tool
The interface of the Balena Etcher tool

The image to be flashed is set on the left, the SD card to be flashed in the centre, and the button to start the flashing operation on the right.

At the end of the operation the SD card will contain two partitions: boot and rootfs. In the device manager on Linux a menu like this appears:

Device menu on Linux
Device menu on Linux

Windows will also show a menu like this: from your file explorer, under This computer you will see the 2 partitions.

Now, with a text editor, create a file on your computer that you will call wpa_supplicant.conf and which you will edit like this:

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=«your_ISO-3166-1_two-letter_country_code»
network={
     ssid="«your_SSID»"
     psk="«your_PSK»"
     key_mgmt=WPA-PSK
}

You will need to replace the following items:

  • «your_ISO-3166-1_two-letter_country_code» with the identifier of your country (for example for Italy it is IT)
  • «your_SSID» with the SSID name of your WiFi network
  • «your_PSK» with the WiFi network password

Then create an empty file that you will call ssh (without any extension).

The new distributions do not have the classic pi user with raspberry as password so, to be able to enter SSH, we must provide another way.

With a working Raspberry we need to create a file called userconf which will contain the user we want to create with an encrypted version of the password we want to assign to him. The format will therefore be username:password-hash.

Suppose we want to keep the user pi, we need to create the password-hash. Suppose we want to create the hash of raspberry as password, again on the Raspberry where we created the userconf file. We must issue the following command from the shell:

echo "raspberry" | openssl passwd -6 -stdin

This command will return the raspberry password hash. For example it could be a string like this:

$6$ROOQWZkD7gkLyZRg$GsKVikua2e1Eiz3UNlxy1jsUFec4j9wF.CQt12mta/6ODxYJEB6xuAZzVerM3FU2XQ27.1tp9qJsqqXtXalLY.

This is the raspberry password hash that I calculated on my Raspberry.

Our userconf file will then contain the following string:

pi:$6$ROOQWZkD7gkLyZRg$GsKVikua2e1Eiz3UNlxy1jsUFec4j9wF.CQt12mta/6ODxYJEB6xuAZzVerM3FU2XQ27.1tp9qJsqqXtXalLY.

PLEASE NOTE: it is necessary to calculate the hash with a Raspberry because the hash calculated with the computer uses another algorithm which would not allow the Raspberry we are preparing to recognize the password.

Alternatively you can download from the link below the userconf file that I created to have a pi user with raspberry as password.

Now open the boot partition on the SD card and copy the three files wpa_supplicant.conf, ssh and userconf into it. Safely remove the SD card from the computer and insert it into the Raspberry.

Turn on the Raspberry, wait a few minutes. To be able to log in to the Raspberry via ssh, you will need to find out what its IP is (the one that the router assigned to it via DHCP).

To do this, simply issue the command from a PC shell:

ping raspberrypi.local 

valid on both Linux and Windows (after installing Putty on Windows).

On my PC the Raspberry responds like this:

Raspberry response to ping
Raspberry response to ping

This makes me understand that the assigned IP is 192.168.43.27. Obviously the IP assigned to your Raspberry will be different.

Alternatively you can use the Angry IP Scanner tool or you can access your router settings to see the devices connected via WiFi and find out what IP the Raspberry has.

To log in to the Raspberry via ssh, issue the shell command:

with raspberry password. On Windows you need Putty. Obviously you will have to use the IP assigned to you.

Once inside the Raspberry, issue the following commands to update the software:

sudo apt update
sudo apt upgrade

The password is raspberry.

Let’s configure the timezone

To configure the timezone, issue the command:

sudo raspi-config

to the Raspberry shell. Suppose you want to set the time zone of Rome (here I will give the example of the time zone of Rome since I live in Italy, you will have to use the time zone of your country).

A screen like this will appear:

Initial screen of the sudo raspi-config command
Initial screen of the sudo raspi-config command

Select the localization option and click OK:

Selected the localization option
Selected the localization option

Then select the timezone option and click OK:

Selected timezone option
Selected timezone option

Now select the geographical area and click OK:

Selected geographic area
Selected geographic area

Finally select the city and click OK:

Selected city
Selected city

Done!

Restart the Raspberry by issuing the command:

sudo reboot

and, after a few minutes, log back into ssh as you did before.

Give the command

date

The Raspberry should now show the correct date and time.

Let’s set the static IP

To ensure that the Raspberry always has the same IP address, we need to set it to be static. In my tests I set it to 192.168.1.190. If we didn’t do this, the router would assign it a different IP at each reboot which would force us to change the IP address of the REST API each time.

We will proceed in two steps:

  • we will set the fixed IP in the Raspberry
  • we will set the router to reserve that address for our Raspberry

For the first point, issue the command:

nano /etc/dhcpcd.conf

to open the dhcpcd.conf file and edit it.

At the end of the file you will need to add a block like this:

interface [INTERFACE]
static_routers=[ROUTER IP]
static domain_name_servers=[DNS IP]
static ip_address=[STATIC IP ADDRESS YOU WANT]/24

where:

  • [INTERFACE] is the name of the WiFi interface (in our case it will be wlan0)
  • [ROUTER IP] is the address of our router (usually it’s something like 192.168.0.1 or 192.168.1.1). You can find it by entering the administration interface of your modem/router
  • [DNS IP] is the address of the DNS server, which generally coincides with the [ROUTER IP] parameter of the modem/router
  • [STATIC IP ADDRESS YOU WANT] it is the IP address that we want to assign as a fixed IP to the Raspberry

So, assuming that [ROUTER IP] = [DNS IP] = 192.168.1.1 and that [STATIC IP ADDRESS YOU WANT] = 192.168.1.190, the block will look like this:

interface wlan0
static_routers=192.168.1.1
static domain_name_servers=192.168.1.1
static ip_address=192.168.1.190/24

Now restart the Raspberry with the command

sudo reboot

and then log in via ssh again, this time with IP 192.168.1.190.

As a second step we will set the router so that it reserves the address 192.168.1.190 for our Raspberry. Each modem/router is different from the others but they are more or less similar. I’ll show here what mine looks like.

To enter I type the address 192.168.1.1 (because my modem has this IP) on the browser and, after giving the administrator password, I arrive at the main screen. From here I have to look for the access control screen.

Adding a static IP for the Raspberry
Adding a static IP for the Raspberry

There will be a button to add a static IP: add the chosen IP combined with the MAC address of the Raspberry WiFi card. However, I recommend that you consult the instruction manual of your modem/router for this operation.

Now check that the Raspberry connects to the network by issuing the command:

ping www.google.com

If you get the response to the ping the network is connected. If you get a message like “Network is unreachable” issue the command

sudo route add default gw [ROUTER IP]  

where [ROUTER IP] is the gateway which in our case is the router IP, i.e. 192.168.1.1

Let’s install some essential tools and the virtual environment

Let’s now proceed by installing some essential tools.

First you will install the sqlite database manager by typing the command:

sudo apt install sqlite3

Then we need to install the pip3 command which allows us to install additional packages and libraries for Python.

On the Raspberry shell issue the command:

sudo apt install python3-pip

which will install pip3.

At this point you must install the command that creates the virtual environment by issuing the following command:

sudo pip3 install virtualenv

Virtualenv , which we discussed in a specific paragraph, is a virtual environment that remains isolated from the rest of the operating system. Once created, it allows us to install packages and libraries of the version we need inside it without them conflicting with the system ones. In this way we can create an environment in which each project can have its own libraries and requirements (with different versions) without the risk of them conflicting with each other and with the system ones.

Now, in the pi user’s home, create the testRFID folder with the command

mkdir testRFID

and enter the folder just created with the command

cd testRFID

Now create the virtual environment with the command

virtualenv env

and activate it with the command

source env/bin/activate

NOTE: when you want to deactivate a virtual environment, simply give the command

deactivate

At this point you need to install some libraries by issuing the following commands:

pip install pyTelegramBotAPI

pip install pytz

pip install RPi.GPIO

python3 -m pip install spidev

python3 -m pip install mfrc522

pip install flask

Now you need to enable the SPI bus so that it can interact with the RFID reader.

Give the command

sudo raspi-config

The main menu will appear:

Main configuration menu
Main configuration menu

Select step 3:

Selecting interface options
Selecting interface options

Go to step 4 to activate SPI:

Select the SPI bus
Select the SPI bus

Confirm by pressing Yes:

We confirm the enabling of the SPI bus
We confirm the enabling of the SPI bus

The confirmation screen will appear:

The SPI bus is enabled
The SPI bus is enabled

We then exit the configuration menu:

Exit the configuration menu
Exit the configuration menu

Download now, from the link below, the python scripts and the database file which you will then have to upload to the Raspberry.

Once you have unzipped the folder, transfer the newly unzipped files to the /home/pi/testRFID folder on the Raspberry.

On Linux you can open a shell on the newly unzipped folder and issue the command:

rsync -avzP * [email protected]:/home/pi/testRFID

On Windows you can use FileZilla as indicated in this link : https://howtoraspberrypi.com/transfer-files-raspberry-ssh/

Once the transfer is done you should find the files app.py, config.py, db_test_rfid_1.db,  teleg.py,  test.py in the testRFID folder as well as the env folder which is part of Virtualenv.

In short here’s what these scripts do:

  • db_test_rfid_1.db it is the file that contains the database. The database contains two tables: users contains the users with their uuid, name and access privileges, accessusers contains all access attempts recorded with the user’s uuid followed by date, time and access privileges
  • test.py it is the file that controls access, activates the relay, the LEDs and the buzzer and sends the notification to the Telegram bot about the access attempt. It also records the access attempt in the accessusers table of the database
  • app.py it is the file that contains the server that uses Flask to manage REST APIs
  • teleg.py it is the file that manages the set of commands coming from the Telegram bot
  • config.py it is the file that contains the Telegram configuration parameters and the timezone. The file you will find in the package contains a fictitious TOKEN and CHAT_ID. In your file you will have to enter the values ​​determined in the paragraph “How to create a Telegram bot”. You will also need to define the TIMEZONE and TIMEZONE_COMMON_NAME parameters for your region/city.

NOTE: the db_test_rfid_1.db file contains, as an initial example, two fictitious users in the users table and some fictitious accesses in the accessusers table. You will be able to manipulate this data with the features of the Telegram bot and/or with the REST APIs.

Usage

At this point open 3 other shells (besides the one already in use) where you will log in to the Raspberry via ssh with the previous command:

and password raspberry.

For each new shell opened, go to the working directory with the command

cd testRFID

and for each one activates the virtual environment:

source env/bin/activate

In the first shell, which you already had open when you installed the libraries and which has the virtual environment already active, launch the test.py script with the command:

python test.py

in the second shell launch the teleg.py script:

python teleg.py

and in the third shell launch the app.py script:

python app.py

If all goes well the system is fully functional in access control with the first script, in Telegram bot control with the second script and in REST API server control with the third script.

The fourth shell is used to check the data in the database.

Give the command:

sqlite3

and the sqlite command shell will open. Giving the command:

.open db_test_rfid_1.db

you will connect to the database (which is actually the db_test_rfid_1.db file).

For example, if you give the command

.tables

the tables present will be listed, i.e. users and accessusers.

If you give the command:

select * from users;

you will be able to see the users with their code, name and access privileges.

If you give the command:

select * from accessusers;

you will be able to see user logins with their code, date, time and access privileges.

For further information on Sqlite commands you can read the official guide at the link https://sqlite.org/cli.html

Functional test

First of all, a clarification is in order. In the following paragraphs we can observe the functioning of the system with the Telegram bot in the first and the functioning with the REST API in the second but it must be underlined that this “division” of the functions was done for simplicity. In reality the features coexist and work at the same time (as long as all scripts are running).

Let’s test access and user management with the Telegram bot

In the following video we can observe the functioning of the system with access control, their registration in the database, notifications on the Telegram bot and user management via the bot. This management is quite simple and follows the examples in the previous paragraph “The set of Telegram commands available”.

Let’s test access and user management with REST APIs

In the following video we can observe the functioning of the system with access control, their registration on the database, user management through the use of REST APIs with the program Postman. Refer to the previous paragraph “The set of REST APIs available” to see the list. It should be underlined that the typical structure of the command to be given on Postman is of the type:

(GET/POST) IP_RASPBERRY:5000/commandname

We initially have the type of API (GET or POST) selected via the drop-down menu to the left of the URL bar. Then follows the IP_RASPBERRY which in our case we have set at 192.168.1.190. The IP is followed by port 5000 on which the Flask server listens. Concludes the actual commandname command (for example readusers or deluser and so on).

In particular, as regards POST-type APIs, they provide a Json document as input data to the API. To provide this Json, you will need to select the Body item under 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. You can see an example in the image below:

Example of POST type REST API with parameters passed via Json
Example of POST type REST API with parameters passed via Json

Let’s look at the scripts

Let’s now examine the three scripts that make up our system.

The test.py script

As already mentioned, it is the script that controls access, activates the relay, the LEDs and the buzzer and sends the notification to the Telegram bot about the access attempt. It also records the access attempt in the accessusers table of the database.

Initially the necessary libraries are imported:

import telebot
import config
import datetime
import pytz
import json
import traceback
import sqlite3
import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522
import time
import datetime

Then the timezone is obtained from the config.py file:

P_TIMEZONE = pytz.timezone(config.TIMEZONE)
TIMEZONE_COMMON_NAME = config.TIMEZONE_COMMON_NAME

The Telegram bot is instantiated and the user is notified (on Telegram) that the script is running:

bot = telebot.TeleBot(config.TOKEN)

bot.send_message(config.CHAT_ID, 'Hi! I\'m online and ready!\nUse the command /help to see the available commands.')

The GPIOs that control the relay, the LEDs and the buzzer as outputs are then defined. The RFID reader is also instantiated:

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT)    # RELE
GPIO.setup(18,GPIO.OUT)    # LED
GPIO.setup(22,GPIO.OUT)    # BUZZER
 
reader = SimpleMFRC522()

Following is the definition of the sanitize function which ensures that the input string is transformed into a two-character string, if it was initially composed of just one character. We need this for a purely aesthetic reason to transform hours, minutes and seconds from objects of the type “1”, “8”, “3” to objects of the type “01”, “08”, “03”:

def sanitize(value):
   if len(value) == 1:
       return "0" + value
   else:
       return value

The empty dictionary is then initialized:

diz = {}

It will be constantly updated with the data present in the users table of the database and will be consulted by the program every time it has to decide whether a certain user can access or not.

An infinite loop now begins which checks whether a badge has been brought near the RFID reader, emits a sound if so, updates the dictionary to decide whether the user is recognized or not and, if so, whether he can enter (activating the relay or not) and writes the access attempt with UUID, date and time and a flag (“g” for granted, “d” for denied and “n” for not recognized) into the accessusers table:

while True:
   # GPIO.cleanup()
    time.sleep(1)
    try:
        status,TagType = reader.read_no_block()
#        print(status)
        if status == None:
            #print ("No Card Found")
            pass
        elif status != None:
            GPIO.output(22,GPIO.HIGH)     #  BUZZER ON
            time.sleep(0.1)
            GPIO.output(22,GPIO.LOW)     #  BUZZER OFF
            dbconnect = sqlite3.connect("db_test_rfid_1.db");
            dbconnect.row_factory = sqlite3.Row;
            cursor = dbconnect.cursor();

            cursor.execute('SELECT * FROM users');
            for row in cursor:
                diz[row['uuidUser']] = row['access']
            print (diz)
            print ("\nCard Found!")
            id,text = reader.read()
            print(id)
            idHex = hex(id)[2:]
            print(idHex) 
            now = datetime.datetime.now() 
            year_s = str(now.year)
            month_s = str(now.month)
            day_s = str(now.day)
            hour_s = str(now.hour)
            minute_s = str(now.minute)
            second_s = str(now.second)

            print("Current year:", year_s)
            print("Current month:", month_s)
            print("Current day:", day_s)
            print("Current hour:", hour_s)
            print("Current minutes:",minute_s)
            print("Current seconds:", second_s)

            qry = "select *  from accessusers"
            cursor.execute(qry)
            lista = []
            for row in cursor:
                boh = int(row['id'])
                lista.append(boh)
            if lista == []:
                num = 1
            else:
                num = max(lista)
                num = num + 1
            if(idHex not in diz):
                print("code not recognized: access denied!")  
                bot.send_message(config.CHAT_ID, 'User ' + idHex + " not recognized: access denied at " + sanitize(day_s) + "/"+ sanitize(month_s) + "/" + year_s + "  " + sanitize(hour_s) + ":" + sanitize(minute_s) + ":" + sanitize(second_s))    
                cursor.execute('''insert into accessusers values (?, ?, ?, ?, ?, ?, ?, ?, ?)''', (num, idHex, day_s, month_s,  year_s, hour_s, minute_s, second_s,  "n"));
                dbconnect.commit();
                GPIO.output(18,GPIO.LOW)     #  LED OFF
            elif(diz[idHex] == "g"):
                print("access granted")
                bot.send_message(config.CHAT_ID, 'User ' + idHex + " recognized: access granted at " + sanitize(day_s) + "/"+ sanitize(month_s) + "/" + year_s + "  " + sanitize(hour_s) + ":" + sanitize(minute_s) + ":" + sanitize(second_s))
                cursor.execute('''insert into accessusers values (?, ?, ?, ?, ?, ?, ?, ?, ?)''', (num, idHex, day_s, month_s,  year_s, hour_s, minute_s, second_s,  "g"));
                dbconnect.commit();
                GPIO.output(18,GPIO.HIGH)     #  LED ON
                GPIO.output(17,GPIO.HIGH)     #  RELE ON
                time.sleep(5)
                GPIO.output(18,GPIO.LOW)     #  LED OFF
                GPIO.output(17,GPIO.LOW)     #  RELE OFF
            elif(diz[idHex] == "d"):
                print("access denied")
                bot.send_message(config.CHAT_ID, 'User ' + idHex + " recognized: access denied at " + sanitize(day_s) + "/"+ sanitize(month_s) + "/" + year_s + "  " + sanitize(hour_s) + ":" + sanitize(minute_s) + ":" + sanitize(second_s)) 
                cursor.execute('''insert into accessusers values (?, ?, ?, ?, ?, ?, ?, ?, ?)''', (num, idHex, day_s, month_s,  year_s, hour_s, minute_s, second_s,  "d"));
                dbconnect.commit();
                GPIO.output(18,GPIO.LOW)     #  LED OFF
            else:
                print("code not recognized: access denied")
            diz = {}
    except: 
        print("errore")
    
   

The teleg.py script

As already mentioned, it is the script that manages the set of commands coming from the Telegram bot.

At the beginning, the necessary libraries are imported, the timezone is loaded from the config.py file and the Telegram bot is instantiated:

import telebot
import config
import datetime
import pytz
import json
import traceback
import sqlite3

P_TIMEZONE = pytz.timezone(config.TIMEZONE)
TIMEZONE_COMMON_NAME = config.TIMEZONE_COMMON_NAME

bot = telebot.TeleBot(config.TOKEN)

Then follow the handlers of the commands that arrive from the bot.

The first is help:

@bot.message_handler(commands=['help'])
def help_command(message):
   bot.send_message(
       message.chat.id,
       'Greetings, here the list of all available commands:\n\n' +
       '*/adduser uuid name permission* adds a user with its privileges (g or d) (example: /adduser a305cd0b60 Paul d)\n\n' +
       '*/moduser uuid name permission* update a user with its privileges (g or d) if already in database (example: /moduser a305cd0b60 Mike d)\n\n' +
       '*/del uuid* removes a user (example: /del a305cd0b60)\n\n' +
       '*/readuser uuid* returns the privileges of the specified user (example /readuser a305cd0b60)\n', parse_mode= 'Markdown'
   )

It gives a list of available commands and, for each, an example of use.

Then we find the del handler which takes care of deleting a user from the database (i.e. from the users table) by giving its UUID as input:

@bot.message_handler(commands=['del'])
def del_command(message):
   arguments = message.text.split(" ")
   if len(arguments) != 2:
       bot.send_message(
           message.chat.id,
           'Wrong number of arguments'
       )
       return
   uuid = arguments[1]
   dbconnect = sqlite3.connect("db_test_rfid_1.db")
   dbconnect.row_factory = sqlite3.Row
   cursor = dbconnect.cursor()
   qry = "delete from users where uuidUser = " + "'" + uuid + "'"
   print(qry)
   cursor.execute(qry)
   dbconnect.commit()
   dbconnect.close()   
   bot.send_message(
       message.chat.id,
       'Deleted ' + uuid + ' user.'
   )

The adduser handler follows which adds a new user to the users table of the database but only if it is not already present (the input parameters are UUID, username and permissions):

@bot.message_handler(commands=['adduser'])
def adduser_command(message):
    arguments = message.text.split(" ")
    if len(arguments) != 4:
        bot.send_message(
            message.chat.id,
            'Wrong number of arguments'
        )
        return
    uuid = arguments[1]
    nameuser = arguments[2]
    permission = arguments[3]

    dbconnect = sqlite3.connect("db_test_rfid_1.db")
    dbconnect.row_factory = sqlite3.Row
    cursor = dbconnect.cursor()

    qry  = "select *  from users where uuidUser = " + "'" + uuid + "'"
    row =  cursor.execute(qry)
    res = row.fetchone() 
    if res is not  None:
        print("user in db")
        bot.send_message(
           message.chat.id,
           'User ' + uuid + ' already in  database. Nothing to do.'
        )

        return 
    print("user not in db")
    qry = "select *  from users"
    cursor.execute(qry)
    lista = []
    for row in cursor:
        boh = int(row['id'])
        lista.append(boh)

    if lista == []:
        num = 1
    else:
        num = max(lista)
        num = num + 1
    cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuid, nameuser, permission));
    dbconnect.commit()
    dbconnect.close()

    bot.send_message(
       message.chat.id,
       'Added ' + uuid + ' user to database.'
   )

Then we have the moduser handler which allows you to modify a user in the users table of the database (but only if it is already present). The input parameters are UUID, username and permissions:

@bot.message_handler(commands=['moduser'])
def moduser_command(message):
    arguments = message.text.split(" ")
    if len(arguments) != 4:
        bot.send_message(
            message.chat.id,
            'Wrong number of arguments'
        )
        return
    uuid = message.text.split(" ")[1]
    nameuser = message.text.split(" ")[2]
    permission = message.text.split(" ")[3]

    dbconnect = sqlite3.connect("db_test_rfid_1.db")
    dbconnect.row_factory = sqlite3.Row
    cursor = dbconnect.cursor()
    num = 0
    qry  = "select *  from users where uuidUser = " + "'" + uuid + "'"
    row =  cursor.execute(qry)
    res = row.fetchone() 
    if res is  None:
        print("user not in db")
        return 
    else:
        num = res[0]

    print("user in db")
    print(num)

    qry = "delete from users where id = " + "'" + str(num) + "'"  
    cursor.execute(qry)
    dbconnect.commit()
    cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuid, nameuser, permission));
    dbconnect.commit()
    dbconnect.close()

    bot.send_message(
       message.chat.id,
       'Modified ' + uuid + ' user in database.'
   )

Finally we find the readuser handler which returns information on the selected user (always if present in the database). The input parameter is the UUID:

@bot.message_handler(commands=['readuser'])
def readuser_command(message):
   arguments = message.text.split(" ")
   if len(arguments) != 2:
       bot.send_message(
           message.chat.id,
           'Wrong number of arguments'
       )
       return
   uuid = arguments[1]
   msg = ""
   dbconnect = sqlite3.connect("db_test_rfid_1.db")
   dbconnect.row_factory = sqlite3.Row
   cursor = dbconnect.cursor()
   qry  = "select *  from users where uuidUser = " + "'" + uuid + "'"
   row =  cursor.execute(qry)
   res = row.fetchone() 
   if res is  None:
       print("user not in db")
       bot.send_message(
          message.chat.id,
          'User ' + uuid + ' not in database.'
       )
       return 
   else:
       qry  = "select *  from users where uuidUser = " + "'" + uuid + "'"
       row =  cursor.execute(qry)
       for row in cursor:
           msg =  row['uuidUser'] + " " + row['name'] + " " +  row['access']


       dbconnect.close()

       bot.send_message(
          message.chat.id,
          msg
       )

The script ends with the function:

bot.polling(none_stop=True)

which keeps the bot in a perpetual state of polling, i.e. continuous interrogation of incoming messages to then be able to manage them with the various handlers.

The app.py script

As already mentioned, it is the script that contains the server that uses Flask to manage the REST API

As always, start by importing the necessary libraries:

from flask import Flask
from flask import request
import json
import sqlite3

Then follows the sanitize function that we have already encountered previously.

The Flask app is instantiated and will handle requests from the REST APIs:

app = Flask(__name__)

Then follow the various REST API handlers.

The first is “/” and returns the classic “Hello world”.

Then follows the getdata handler mapped to the POST-type /getdata API which returns the accesses that occurred on a given day (given as a Json containing year, month and day as input):

@app.route('/getdata',  methods=['POST'])
def getdata():
    request_data = request.get_json()
    year_j = request_data['year']
    month_j = request_data['month']
    day_j = request_data['day']
    print(year_j + "/" + month_j + "/" + day_j)

    dbconnect = sqlite3.connect("db_test_rfid_1.db")
    dbconnect.row_factory = sqlite3.Row
    cursor = dbconnect.cursor()
    qry = "SELECT  * from accessusers where year = " + str(year_j) + " and month = " + str(month_j) + " and  day = " + str(day_j)
    cursor.execute(qry)
    innerdata = {}
    outerdata = {}
    for row in cursor:
       hms = sanitize(str(row['hour'])) + ":" + sanitize(str(row['minutes'])) + ":" + sanitize(str(row['seconds']))
       uuid = str(row['uuidUser'])
       acc = str(row['access'])
       innerdata[uuid] = acc
       outerdata[hms] = innerdata
       innerdata = {}
    dbconnect.close()
    json_data = json.dumps(outerdata)

    return  json_data

We then have the readusers handler mapped to the /readusers GET API which returns the list of users present in the users table of the database:

@app.route('/readusers',  methods=['GET'])
def readusers():
    dbconnect = sqlite3.connect("db_test_rfid_1.db")
    dbconnect.row_factory = sqlite3.Row
    cursor = dbconnect.cursor()
    qry = "SELECT  * from users"
    cursor.execute(qry)
    innerdata = {}
    outerdata = {}
    for row in cursor:
        idrow = row['id']
        lista = [row['uuidUser'], row['name'], row['access']]
        innerdata = lista
        lista = []
        outerdata[idrow] = innerdata
        innerdata = {}
    dbconnect.close()
    json_data = json.dumps(outerdata)

    return  json_data

We then encounter the insertuser handler mapped to the POST-type API /insertuser which inserts a new user into the users table of the database (if not already present) with UUID, name and access privileges:

@app.route('/insertuser',  methods=['POST'])
def insertuser():
    request_data = request.get_json()
    uuidUser_j = request_data['uuidUser']
    name_j = request_data['name']
    access_j = request_data['access']

    dbconnect = sqlite3.connect("db_test_rfid_1.db")
    dbconnect.row_factory = sqlite3.Row
    cursor = dbconnect.cursor()
 
    qry  = "select *  from users where uuidUser = " + "'" + str(uuidUser_j) + "'"
    row =  cursor.execute(qry)
    res = row.fetchone() 
    if res is not  None:
        print("user in db")
        return 'user in db'
    print("user not in db")
    qry = "select *  from users"
    cursor.execute(qry)
    lista = []
    for row in cursor:
        boh = int(row['id'])
        lista.append(boh)

    if lista == []:
        num = 1
    else:
        num = max(lista)
        num = num + 1
    cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuidUser_j, name_j, access_j));
    dbconnect.commit()
    dbconnect.close()
    return 'Done!'

We arrive at the deluser handler mapped to the /deluser POST-type API which deletes a user (identified by a certain UUID) from the users table of the database (if present in the database itself):

@app.route('/deluser',  methods=['POST'])
def deluser():
    request_data = request.get_json()
    uuidUser_j = request_data['uuidUser']
    dbconnect = sqlite3.connect("db_test_rfid_1.db")
    dbconnect.row_factory = sqlite3.Row
    cursor = dbconnect.cursor()
    qry = "delete from users where uuidUser = " + "'" + str(uuidUser_j) + "'"
    print(qry)
    cursor.execute(qry)
    dbconnect.commit()
    dbconnect.close()
    return 'Done!'

We end with the moduser handler mapped to the POST-type API /moduser which modifies a user already present in the users table of the database (with the UUID, name and access privileges data provided via Json):

@app.route('/moduser',  methods=['POST'])
def moduser():
    request_data = request.get_json()
    uuidUser_j = request_data['uuidUser']
    name_j = request_data['name']
    access_j = request_data['access']

    dbconnect = sqlite3.connect("db_test_rfid_1.db")
    dbconnect.row_factory = sqlite3.Row
    cursor = dbconnect.cursor()
    num = 0
    qry  = "select *  from users where uuidUser = " + "'" + str(uuidUser_j) + "'"
    row =  cursor.execute(qry)
    res = row.fetchone() 
    if res is  None:
        print("user not in db")
        return 'user not in db'
    else:
        num = res[0]

    print("user in db")
    print(num)

    qry = "delete from users where id = " + "'" + str(num) + "'"  
    cursor.execute(qry)
    dbconnect.commit()
    cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuidUser_j, name_j, access_j));
    dbconnect.commit()
    dbconnect.close()

    return "Done!"

The script ends with the part that launches the Flask application and waits for requests from the client on the Raspberry’s IP (in our case 192.168.1.190) on port 5000:

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

Final remarks

However, the scripts mentioned above are interrupted in their execution if we close the shell they belong to. For example, if I close the shell where I launched the test.py script, it will be killed (its execution will be terminated). To ensure that the execution of our scripts is not interrupted when the shell to which they belong is closed, we can launch them in a slightly modified way. In this case, a single shell is sufficient (obviously positioned inside the work folder and with the virtual environment active) and issuing, in succession, the following commands:

nohup python test.py &

nohup python teleg.py &

nohup python app.py &

This way they will continue to run even if we close the shells. We could only stop them with the kill command applied to the PID of each running script or by turning off (or restarting) the Raspberry.

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