:::info
# RFID access control system with LoPy
:::
A project by Paolo Molinaro (pm222py).
This project aims to create a baseline for how to create an RFID access control system that can be implemented in any place with wireless capability and battery support, the latter in case the implementation needs to be in a position where wired power supply is not in place or for power backup in case the power supply it temporary not available.
In its current state this project is not optimized for power saving or backup power mode but it can easily be modified to accomplish that.
Time approximation to complete the project: 3 hours
## Objective
Physical access control systems (PACS) are a type of physical security designed to restrict or allow access to a certain area or building. Often, PACS are installed in order to protect businesses and property from vandalism, theft, and trespassing, and are especially useful in facilities that require higher levels of security and protection. Unlike physical barriers like retaining walls, fences or strategic landscaping, physical access control procedures control who, how and when a person can gain entry.
This project aims to provide a simplistic solution that relies on an RFID card or tag for personal credentials, a reader placed on the access point to validate the credentials and send them to a monitoring system that logs the access attempts and alerts the administrator in case of an unauthorized access attempt, hence creating an auditable registry.
The device is also equipped to alert in real time in case of tampering or device errors, restoring itself online in a matter of seconds.
For this proof of concept, only the card ID will be read and validated, hence I will not treat authenticating sectors A and B of the classic Mifare cards, more information [here](https://learn.adafruit.com/adafruit-pn532-rfid-nfc/mifare).
## Materials
Three different list of materials are provided, the basic list consists of everything that will be needed for the proof of concept. The extended list of materials consists of improvements of the device, while the lock-side materials are what is needed to implement the device in a real environment.
*Note: For this proof of concept a (220ohm or 330ohm) resistor and an LED are needed to simulate the solenoid lock, those can be bought in any store easily without any precision requirements for the components.*
Basic list of materials (≈77€):
| Name | Description | Image | Price | Buy |
| --------- | ---- | --------- | ------ | --- |
| LoPy4 | A small development board capable of being programmed with MicroPython. This board is able to connect to various networks such as WiFi, Blueooth, LoRa and Sigfox. | ![](https://i.imgur.com/eE4VvHp.png) | ≈39€ | [PyCom](https://pycom.io/product/lopy4/) |
| Expansion Board 3.0 | Provides an easy way to connect things like sensors and battery to the LoPy4. Also has an USB-port which makes interfacing with, and powering the LoPy4 significantly easier. | ![](https://i.imgur.com/JEQgbUQ.jpg) | ≈18€ | [PyCom](https://pycom.io/product/expansion-board-3-0/) |
| RFID MIFARE RC522 | Highly integrated reader/writer for 13.56 MHz contactless communication, with an internal transmitter that can drive a reader/writer antenna designed to communicate with ISO / IEC 14443 A / MIFARE cards and transponders without additional active circuitry. | <img src="https://i0.wp.com/www.teachmemicro.com/wp-content/uploads/2018/12/arduino-rfid-rc522.png?fit=800%2C572&ssl=1" width="200"/> | ≈8€ | [Amazon.it](https://www.amazon.it/dp/B01M28JAAZ) |
| Jumper wires | Are used to connect the sensors to the LoPy. | ![](https://i.imgur.com/B63H8Fl.png) | ≈5€ | [Electrokit](https://www.electrokit.com/en/product/jumper-wires-20-pin-30cm-female-male/) |
| Breadboard | Is used to connect the sensors to the LoPy. It is possible to make the project without the breadboard. | ![](https://i.imgur.com/C7rYMwp.png) | ≈7€ | [Electrokit](https://www.electrokit.com/en/product/solderless-breadboard-400-tie-points/) |
Extended list of materials (≈33€):
| Name | Description | Image | Price | Buy |
| --------- | ---- | --------- | ------ | --- |
| Anker Powerbank 10000mAh | Enables the device to run without being connected to an outlet. Easy to charge and most people have them available for free at home. | ![](https://i.imgur.com/qew4Hnt.jpg)| ≈25€ | [Amazon.it](https://www.amazon.it/Power-Anker-Batteria-Portatile-PowerCore/dp/B019GJLER8) |
| External WiFi Antenna | To extend the WiFi range capabilities of the device. | ![](https://i.imgur.com/KUhRSjJ.png)| ≈8€ | [PyCom](https://pycom.io/product/external-wifi-antenna/) |
Lock-side materials (≈31€):
| Name | Description | Image | Price | Buy |
| --------- | ----- | --------- | ------ | --- |
| Relay 12v (5mA trigger)| Allows a low current flow circuit to control a high current flow circuit. |![](https://i.imgur.com/8GWVyLB.jpg) | ≈8€ | [Amazon.it](https://www.amazon.it/Scheda-accoppiatore-optoisolatore-livello-Arduino/dp/B07PZC9ZMM) |
| Solenoid lock 12v | Lock that normally is active (solenoid slug in the way) and when 9-12VDC is applied, the slug pulls in so it doesn't stick out anymore and the door can be opened. | ![](https://i.imgur.com/yZqCEJL.jpg)| ≈13€ | [Amazon.it](https://www.amazon.it/Solenoide-serratura-elettrica-armadietto-sicurezza/dp/B074MX923N) |
| Transformer AC 220V to DC 12V | Power supply transformer to power the solenoid lock. | ![](https://i.imgur.com/CVYVEBy.jpg)| ≈11€ |[Amazon.it](https://www.amazon.it/NUOVA-GERMANY-Alimentatore-Professionale-Trasformatore/dp/B09MJHC7QV)|
## Computer setup
The development environment consists of Atom as the personal choice of IDE. This choice is deeply connected to the compatibility with Pycom and their plugin Pymakr. This [guide](https://docs.pycom.io/gettingstarted/) will help to get started.
### What you need to do to set up the development environment:
1. Install your choice of IDE. ([Atom](https://atom.io) / [VSC](https://code.visualstudio.com))
2. Install Node.js (if required for your setup). ([Node.js](https://nodejs.org/en/download/))
3. Install Pymakr plugin. ([Pymakr](https://pycom.io/products/supported-networks/pymakr/))
4. Close the IDE.
5. Connect the microcontroller and make sure the firmware of the device is updated, if not, update according to device documentation ([LoPy documentation](https://docs.pycom.io/updatefirmware/)).
6. After the firmware is updated, start the IDE.
7. If using Atom, the Pymakr-plugin should start automatically and the REPL (microcontroller command line). The console should as shown below and the device can be tested with simple python commands:
```
>>>
>>>1+1
2
>>>
```
8. It is suggested to turn on the "Auto connect" and "Safe-boot before upload" that can be found in the settings bar on the right side of the terminal, as shown below.
![](https://i.imgur.com/VpA63mr.png =600x)
9. The code can now be uploaded via USB to the device. The project needs to be open and selected, the device connected as well as selected. Therefore, by just clicking on the third icon "Upload project to the device", that can be found on the left side of the terminal, the project will be uploaded to the microcontroller.
:::info
Remember that `boot.py` and `main.py` are the filenames that will be read in that order by the microcontroller.
:::
### What you need to do to set up the data broker, visualization and alert.
Docker Compose is used to run the personalised stack (MQTT + Node-Red + InfluxDB + Grafana) on our local machine. Describing [Docker](https://www.docker.com/) and Docker Compose in-depth is out of scope for this tutorial. However, in short, with Docker there is no need to download and install various software. Instead, Docker downloads the software in the form of Docker images and runs it as processes in containers on the machine. The infrastructure can be started and stopped using the two commands listed below:
- *docker-compose up*
- *docker-compose down*
The docker compose for the containers will be provided in the [Platform](https://hackmd.io/RqThNf-ZQm-HK-C_jOY7PQ?view#Platform) section with link to detailed guides.
## Putting everything together
The circuit diagram below shows the connections needed for the proof of concept.
![](https://i.imgur.com/Zy2ucL0.png)
The following circuit diagram describes the connections needed for a field installation.
![](https://i.imgur.com/Ujuru6z.png)
In both cases the Pin 10 of the expansion board delivers the signal actuate the solenoid lock (that in the proof of concept is represented by an LED). The transformer is needed to deliver 12V DC to the solenoid lock to make it work. The RFID-RC522 connection can be visualised better in the following table:
| LoPy4 Pin | RFID-RC522 Pin | Wire colour |
| --------- | -------------- |------------ |
| 3.3v | 3.3v | Red |
| GND | GND | Black |
| P22 | RST | Orange |
| P14 | MISO | Yellow |
| P11 | MOSI | Blue |
| P23 | SCK | Green |
| P9 | SDA | White |
### Electrical theory and calculations
#### How a transformer works
Transformers are based on the principle of **mutual-induction**, meaning: when current flows through a wire a magnetic field is produced and if another wire is submerged in the magnetic field, a current will flow in the wire. A basic transformer consists of two separate windings of insulated wires wound around a common iron core. The power source or supply is hooked to the primary winding, the load (in our case the solenoid lock) to be served is hooked to the secondary winding. When the primary winding is energized an electromagnetic field builds up and then collapses in the iron core, this field cuts through the secondary coil winding inducing power to the load hooked to the secondary. By altering the number of windings on the primary and secondary, we can alter the amount of volts and amps between the source and the load.
$$ V_{primary}/V_{secondary} = N_{primary}/N_{secondary} $$
where N is the number of windings.
Hence to have 12V on the secondary with 230V on the primary the minimum values needed for the number of winding is 115 on the primary and 6 on the secondary. The problem for us is that the transformer will not convert an alternate current (AC) from the line to a direct current (DC) needed to activate the lock.
Hence, a more complex circuit needs to be implemented after the secondary winding, that may look like this:
![](https://i.imgur.com/lMZJ0xC.png)
1. The ACV comes to rectifier bridge, D3 through D4, rectifies the AC into pulsating DC, here the threshold voltage can be ignored since the stabilized output tension will just depend on the BJT and the Zener diode.
2. The capacitor C1 will smooth the resulting pulsating DC out of the full wave rectifier diode bridge into a steady DC.
3. This DC flows through resistor R1 and Zener diode D5 (Zener tension 12V).
4. The R1, calculated according to [the standard emitter follower transistor usage](https://en.wikipedia.org/wiki/Voltage_regulator#Transistor_regulator), reduce a current for D5 until it has a constant voltage at VZ of 12V at the base of transistor Q1, since it is in a common collector BTJ, therefore:
$$V_{out} = V_{D5} – V_{BE}$$
$$V_{Be} = 0.6V$$
$$V_{out} = 12V – 0.6V = 11.4V$$
5. The voltage of base(VB) equals the voltage of emitter(VE) and while the BJT is conducting, the electrical current flows through B-E, creating a voltage across B-E of 0.6V. Therefore, the output voltage is reduced to 11.4V that will be constant due to the gain of Q1.
#### How an RFID works
Passive RFID tags utilize an induced antenna coil voltage for operation. By providing an energizing RF signal, a reader can communicate with a remotely located device that has no external power source such as a battery. Since the energizing and communication between the reader and tag is accomplished through antenna coils, it is important that the device must be equipped with a proper antenna circuit for successful RFID applications.
In our case, for 13.56 MHz passive tag applications, a few mH of inductance and a few hundred pF of resonant capacitor are typically used. The voltage transfer between the reader and tag coils is accomplished through inductive coupling between the two coils. As in a typical transformer, where a voltage in the primary coil transfers to the secondary coil, the voltage in the reader antenna coil is transferred to the tag antenna coil and vice versa.
To read more about the topic: [Antenna Circuit Design for RFID Applications](https://ww1.microchip.com/downloads/en/AppNotes/00710c.pdf)
## Platform
The platform chosen for this project is a self-hosted, free and open source solution, all deployed locally with [Docker](https://www.docker.com/). The entire platform is based on four services:
1. [MQTT](https://mqtt.org/) is a lightweight communication protocol especially suited for IoT. We will use it in this scenario to send the reader data (payload) from our microcontroller to our MQTT broker (MQTT server).
2. [Node-Red](https://nodered.org/) is a programming tool for wiring together hardware devices, APIs and online services in new and interesting ways. It provides a browser-based editor that makes it easy to wire together flows using the wide range of nodes in the palette that can be deployed to its runtime in a single-click.
3. [InfluxDB](https://www.influxdata.com/products/influxdb/) is the database we are going to use for this project. InfluxDB is a time series database and works perfect to store time defined data like our access logs.
4. [Grafana](https://grafana.com/) is a data monitoring solution where it is possible to easily set up any kind of dashboard for your data. We will use it set up a table of access attempt events, ordered in time with the relative identification and status of access.
![](https://i.imgur.com/0ZME7e4.jpg)
The data is sent from the microcontroller via [MQTT](https://mqtt.org/) to a self-hosted MQTT-server, since the connectivity will be strictly on WiFi for reliability and security purposes. Therefore, [Node-Red](https://nodered.org/) acts at the same time as middleware for sending the data to be stored in [InfluxDB](https://www.influxdata.com/products/influxdb/) and generates the alert via a [Discord webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) in the event of unauthorized access. The logs data stored in the database then is visualised in [Grafana](https://grafana.com/).
:::info
I have chosen to create a personalised solution with a docker-compose file that can be found [HERE](https://github.com/greedpanda/RFID-access-control/blob/main/docker-stack/docker-compose.yml), since it is a free local solution to implement keeping in mind security. The setup is described in my GitHub repository.
:::
The introduction of [Node-Red](https://nodered.org/), *instead of the usual Telegraf with a TIG-stack*, is due to the impossibility of Grafana alerts to be triggered on instant readings, but only with continuous-monitoring type of data, intercepting the payload from the MQTT broker, and alerting me with a webhook was the best solution for instant alerts.
The configuration of the nodes in Node-Red is shown in the figure below and the plugin nodes to install are:
- [InfluxDB](https://flows.nodered.org/node/node-red-contrib-influxdb);
- [Discord webhook](https://flows.nodered.org/node/node-red-contrib-discord-advanced).
![](https://i.imgur.com/sq8uX5A.png)
## The code
:::success
The code used for this proof of concept is published on [GitHub](https://github.com/greedpanda/RFID-access-control).
:::
:::warning
Before running the code remember to set up the [config.json](https://github.com/greedpanda/RFID-access-control/blob/main/rfid%20/config.json) and the [docker-stack](https://github.com/greedpanda/RFID-access-control/tree/main/docker-stack) configuration files.
:::
Two libraries are necessary for this project:
- [MQTT](https://github.com/pycom/pycom-libraries/blob/master/lib/mqtt/mqtt.py) to connect to the MQTT Broker.
- [MFRC522](https://github.com/wendlers/micropython-mfrc522/blob/master/mfrc522.py) to interface with the RFID reader.
[My version of the MFRC522 library](https://github.com/greedpanda/RFID-access-control/blob/main/rfid%20/lib/mfrc522.py) has been cleaned-up to be adapted to the LoPy and to reduce the amount of code to be uploaded as much as possible.
The [boot.py](https://github.com/greedpanda/RFID-access-control/blob/main/rfid%20/boot.py) file is constructed to reduce as much as possible power wasting, disabling Bluetooth and the LED heartbeat, while at the same time connect to WiFi since it will be vital for the communication of the device.
A [config.json](https://github.com/greedpanda/RFID-access-control/blob/main/rfid%20/config.json) file is present to configure the WiFi and MQTT connections.
The main.py file, shown below, consists in all the logic needed for the project.
In this proof of concept, I have authorized only the Admin UID to access and consequently open the door (signal lasting 1 second set on Pin 10). Every access attempt is registered in the`logger` dictionary together with the status `0 = LOGOUT, 1 = LOGIN, 2 = UNAUTHORIZED`.
When the access is authorized the board will flash the LED in Green sleeping for 1 second to avoid consecutive readings; if the access in unauthorized the board will flash the LED in Red, blocking the attempt to access for 5 seconds, while sending the alert.
If an error occurs, meaning: generic device error, authentication error (block A, B reading) or unsupported tag, a different payload will be sent to MQTT that will be parsed to alert the user of the error occurred and the device will be resetted to be operative after a few seconds.
### main.py
```python=0
from lib.mqtt import MQTTClient
from lib.mfrc522 import MFRC522
from machine import Pin
import hashlib
import json
import pycom
import time
import ubinascii
# Initialising pin 10 as signal output to unlock the door.
open_door = Pin('P10', mode = Pin.OUT)
# Loading configuration file.
with open('config.json') as f:
config = json.load(f)
# Admin is the only one authorized.
authorized = '0x03536fad'
logger = {}
# Status for each access attempt
# 0 = LOGOUT, 1 = LOGIN, 2 = UNAUTHORIZED
def status(uid):
pycom.heartbeat(False)
# insert uid if not present
if uid not in logger:
logger[uid] = 0
if uid != authorized:
print("Access denied, sending alert...")
pycom.rgbled(0xff0000)
logger[uid] = 2
else:
print("Authorized access of", uid)
pycom.rgbled(0x00ff00)
# open door signal lasts 1s according to lock specs
open_door.value(1)
time.sleep(1)
open_door.value(0)
# change status (logged in / out)
if logger[uid] == 1:
logger[uid] = 0
else:
logger[uid] = 1
# Set status and send uid:status to MQTT broker
def send_value(uid):
status(uid)
try:
c.publish(topic_pub,'{"uid":'+ str(int(uid, 16)) +',"status":' + str(logger[uid]) +'}')
except Exception as e:
print('Failed to send! ', e)
# RFID reader
def do_read():
# MFRC522(SCK, MOSI, MISO, RST, SDA)
rdr = MFRC522('P23', 'P11', 'P14', 'P22', 'P9')
try:
while True:
(stat, tag_type) = rdr.request(rdr.REQIDL)
if stat == rdr.OK:
(stat, raw_uid) = rdr.anticoll()
# Get UID
try:
uid = ("0x%02x%02x%02x%02x" % (raw_uid[0], raw_uid[1], raw_uid[2], raw_uid[3]))
except Exception as e:
print("Device error:", e)
error_alert(0)
# if the card is successfully read
if stat == rdr.OK:
print("Card detected - uid:", uid)
if rdr.select_tag(raw_uid) == rdr.OK:
key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
if rdr.auth(rdr.AUTHENT1A, 8, key, raw_uid) == rdr.OK:
send_value(uid)
if logger[uid] == 2:
# 5sec timout for unauthorized attempt
time.sleep(5)
else:
time.sleep(1)
pycom.heartbeat(False)
else:
print("Authentication error")
error_alert(1)
else:
print("Failed to select tag")
error_alert(2)
rdr.stop_crypto1()
except KeyboardInterrupt:
print("Bye")
# Error alert function to create Object needed for the webhook
def error_alert(error_type):
c.publish(topic_pub,'{"errType":' + str(error_type) + '}')
pycom.rgbled(0xff8000)
time.sleep(0.5)
machine.reset()
# Placeholder function for needed callback
def sub_cb(topic, msg):
print((topic, msg))
# MQTTServer topic declaration
topic_pub = 'access-control/'
topic_sub = 'access-control/log'
broker_url = config['hostname_mqtt']
client_name = ubinascii.hexlify(hashlib.md5(machine.unique_id()).digest()) # create a md5 hash of the pycom WLAN mac
c = MQTTClient(client_name,broker_url,user=config['user_mqtt'],password=config['pass_mqtt'], keepalive=3600)
c.connect()
c.set_callback(sub_cb)
c.subscribe(topic_sub)
do_read()
```
## Transmitting the data / connectivity
The data is transmitted over WiFi in order to connect to the internet and reach the MQTT server, since the device is meant to be used indoor and in a fixed position, there was not the need of using any other type of connection that would have made the implementation difficult and less secure.
One of the major benefits of WiFi is that the amount and size of the transmitted data is of little to no concern. While the current state of the project is not handling large data, since the packet sent over consists of a json object (that can be technically be skimmed down to a tuple of values to decrease the size even more and then reassembled in Node-Red) this allows us to expand further down the line without worrying about it, making it possible in the future to implement a complete authentication based on the data blocks A and B of the Mifare cards.
The data is sent each time the reader is triggered, therefore there is no possibility of estimating the power consumption, or the medium amount of data sent, without a study of the user’s behaviour after deployment.
As the device is meant to be deployed in a fixed position indoors, powered by USB or a fixed power supply, there is no concern about power consumption and WiFi power demands.
The communication relies on MQTT that is a lightweight, publish-subscribe, machine to machine network protocol. It is designed for connections with remote locations that have devices with resource constraints or limited network bandwidth. It must run over a transport protocol TCP/IP that provides ordered, lossless, bi-directional connections.
The publication of the data is picked up by Node-Red that, beside acting as a middleware for InfluxDB, activates an alert with a Discord webhook via an HTTP Request as already shown.
## Presenting the data
The data gathered is presented in [Grafana](https://grafana.com/) in the form of a table ordered by last triggered event, as shown in the figure below. The data is stored in [InfluxDB](https://www.influxdata.com/products/influxdb/) in a permanent way, since it should be auditable anytime for any violation that may occur. I have chosen InfluxDB because is an open source, time series platform; this means that is optimized to store all data serializing it in time; I have also chosen it because it is performing and simple to interface with Node-Red and Grafana. Any other database would have been acceptable until it presents an interface with Node-Red and Grafana, but probably any other choice would have been not as performing as InfluxDB.
![](https://i.imgur.com/iu8AI4F.png)
### Grafana setup
To construct the output shown in the figure above, a few steps are needed to join the Identification data and the Status data, and to visualise a readable output. The steps are the followings:
1. create a new dashboard;
2. add a new panel and go to **edit panel**;
3. in the **Query** tab, create two queries to select respectively "uid" and "status" from the payload received, setting the respective alias to be shown for each field, as shown below;
![](https://i.imgur.com/1HKKMnR.png =400x400)
4. in the **Transform** tab, select **Outer join** and join by the field name **Time**, as shown below;
![](https://i.imgur.com/gYRActm.png)
5. on the right side, under the **Field** tab, it is possible to set the **Value mappings** to match the status output wanted (e.g. mapping Value:"0" to Text:"LOGOUT");
6. still under the **Field** tab, it is possible to set the **Thresholds** to colour the output as desired (e.g. "1" with a green colour to show the LOGIN in green since the value "1" is mapped to "LOGIN").
*Note: the steps are similar to configure the Error log, without the need of step 3-"Outer join".*
### Automation and triggers
As already described at the end of the [Platform](https://hackmd.io/RqThNf-ZQm-HK-C_jOY7PQ?view#Platform) section, the automation and triggers are handled by Node-Red. The payload is parsed through two switch nodes at the same time, one for the unauthenticated access attempt and one for the errors alert.
:::warning
The code present in the function nodes is as follows (with minor differences for the error parsing):
```javascript
var date = new Date();
msg.payload = {
"embeds": [
{
"type": "rich",
"title": `Unauthorized access attempt`,
"color": 0xff0000,
"fields": [
{
"name": `Identification`,
"value": String(msg.payload.uid)
}
],
"timestamp": date.toISOString()
}
]
}
msg.headers = {'Content-Type': ['application/json']}
return msg;
```
:::
The result is the following shown set of alerts in the discord channel configured with the corresponding webhook, triggered by the HTTP response node.
![](https://i.imgur.com/9BA9xR9.png =200x100) ![](https://i.imgur.com/kTyIiSv.png =200x100) ![](https://i.imgur.com/1Htmser.png =220x100)
## Finalizing the design
The final design is in line with the expectations, the proof of concept being the simplification of the intended built, has room for improvement as mentioned in the entire document, with the further improvements of reducing the power consumption with the use of the reader interrupt.
Another improvement that can be implemented is to have the ability to write into the tags and to use the complete authentication capability of the Mifare tags.
### Final result - quick demo
Here you can see the device in action.
{%youtube QuFJ8wJGYQQ %}
### Overview of the project
The device used for the proof of concept.
![](https://i.imgur.com/mj2V6IY.jpg)