Building an open HID card reader

Quite some time ago I bought some HID proximity card readers from eBay with the intent of installing them in my old house to manage access to the front and back door. In the end, all I did was write this bit of code to decode the Wiegand interface signals that the card reader sends. I decided that my DIY skills were nowhere where they needed to be to allow me to start installing electric door strikes!

Recently we moved to a new house which has an outdoor storage room where we keep tools and other bits and pieces. I want to lock the door due to the value of some of the items we have in there, but it is a pain to have to carry keys all the time. So I decided to come up with a digital solution. My requirements were:

  • I should still be able to use the key (in case of system/power failures)
  • It should expose an API to allow the door to be unlocked/locked
  • It should allow some kind of token to be used to open the door (without the key)
  • It should have no external (cloud/internet) dependencies
  • Adding/removing tokens should be easy

At this point I remember the HID access card readers and electric door strike I bought all that time ago and decided: now is your time to shine!!

Of course, I also needed to throw in a few software goodies, and settled on my pretty quiet home k8s cluster as the place I'd run the software components.

The high level design

block diagram

As a very simple level I have a Raspberry Pi 3B+ powered using a PoE hat which is connected to an Ardunio using USB. I'm using a USB to serial cable which also allows me to recover the 5V power from USB - so I can communicate with and power my door lock circuit with a single cable.

When the HID reader detects a card code the Arduino decodes the information presented and then sends it via the serial port to the Raspberry Pi. On the Pi there's an application running which picks up the data and places it on an MQTT topic. Off in 'the cloud' there's an application which picks up that message from the MQTT topic and then checks it against a set of allowed cards (stored in Redis). If the card is allowed then an 'open' message is placed on another topic. The application on the Pi is also subscribed to this topic, so when it sees an open message it sends a message to the Ardunio and starts a timer. The Ardunio on seeing this message puts on of its output pins high which activates a relay which allows current to flow to the door strike - and yay! The door is open. After the timer expires the topic is updated and the lock closes.

This might look like overkill for a simple mechanism to manage opening one door, but I wanted it to be easily extendable and I'm also planning to deploy more devices which use MQTT so I wanted to have a good set of base components to work from for my future IoT/smart home plans.



The electronics are very simple it is an ATmega328 P chip connected to a USB serial interface, the HID reader and a relay which controls the power to the door strike. The relay is connected via a transistor so we don't draw too much current through the digital pins of the ATmega. The circuit diagram is below:

circuit diagram

Although I use the 'Uno' board for my prototyping I don't like to use them in deployments, instead I like to put the chip onto a circuit board along with the other bits and pieces needed. I like to use veroboard to make my boards because it is simple. Here is my design for the board:

veroboard design

Here's my final board... happily my soldering is getting better as well :)

circuit board

I covered putting an Arduino on a circuit board in more detail in this post https://rjk.codes/post/building-a-keyboard-with-arduino/


Recently I got an Ender 3 v2 3D printer. I got it for a number of different uses, but one was to create enclosures for my electronics projects. So I decided to use it for this.

This is the first time I've done any real work with a 3D printer, so I needed to learn quite a bit about the process - and I still have much to learn!

In the end I followed some examples to create a parametric box in FreeCAD. This is a box whose dimensions are driven by changeable parameters - so I can use it for future projects as well. I also added some holes which were specific to the cables I needed to allow through the walls of the box for this project.

3d drawing of lock enclosure

I exported STL files from FreeCAD and then sliced them using the free version of Cura.

The 3D printing process was frustrating for me as a beginner, I learned a lot and could probably write a whole other article on that. The print I got in the end is not perfect due to warping, but it is good enough for what I need. Here is the final product:

finished 3d printed box

I did upload my box to thingiverse if you'd like to take a closer look at how it works. The lid is fixed on with M4 bolts and I used a carbon tap to make the thread for the bolts in the holes.


K8s dependencies

There are some 'support' components I'm running on my local K8s cluster:

  • MQTT broker
  • Redis key/value pair store

WARNING: these are not production ready deployments, security is not a main concern for me as I rely on my network security to prevent unwanted access


I'm using the Eclipse Mosquitto MQTT broker in a very simple 1-node deployment. I put the yml files for my deployment in this Gist, this has no security enabled and expects to be able to find a persistent volume claim called mqtt-vol-claim.


The redis key/value pair store also has a very simple setup, although this one does have a password. The code to deploy it is in this Gist. Make sure you update the password value and create a persistent volume claim called redis-vol-claim.

Door Interface Application

This is the application which runs on the Raspberry Pi which is connected to the Arduino circuit. It is written in Python and runs in a docker container for maximum portability.

The code is here https://github.com/richardjkendall/door-interface.

As much of what the application does is asynchronous it utilises a number of internal queues (using the good old Python Queue library). When serial messages are received they are enqueued by the serial processing thread and another thead takes them off the queue, processes them and then queues them for delivery to the MQTT broker. When messages are received from the MQTT topics they to are queued an another processor picks them up and takes action based on them. The app architecture is shown below. I imagine there was a more elegant way to do this, managaing all the queues and threads did become a pain.

App block diagram

Decoding the card data

As I mentioned earlier, the physical layer card decoding is handled by the code running on the Arduino. It sends a HEX representation of this data on the serial line to the application runing on the Raspberry Pi.

There are various formats of card, the most common seem to be the open 26-bit format and the 35-bit HID Corporate 1000 format. There are more details about the formats here.

My cards are all in the open 26-bit format. Each card has 26 bits of data, structured as follows:

               1       2
1      8       6       4


P = parity bits, first one even, second one odd
F = facility code (8 bits), max is 255
N = card code (16 bits), max is 65,535

I use a combination of the facility code and card code to make a unique card value which is then used for lookups in the card database.

Deploying the door interface application

This app is packaged as a docker container and run on the Raspberry Pi via docker. I like this method as it keeps it clean and manages all the dependencies inside the container image.

Running in this manner does mean mounting the USB serial port on the running container. Mine was called ttyUSB0 but yours might have a different name (I often use dmesg after plugging something in to find the name).

Also - if you want to use this you'll need to build it yourself rather than use the image in my private registry.

docker run --name door-interface --device /dev/ttyUSB0 --restart always -d \
 -e DOOR_NAME=*** \
 -e COM_PORT=/dev/ttyUSB0 \
 -e MQTT_BROKER_HOST=mqtt.services.*** \
 -e MQTT_DOOR_STATUS_TOPIC=house/storage/***/hidreader \
 -e MQTT_DOOR_LOCK_TOPIC=house/storage/***/lock \
 -e FAKE_SERIAL=off ***.richardjameskendall.com/door-interface:latest

Door Access Manager Application

The final piece of the software puzzle is an application which runs on K8s and which picks up messages from the door HID readers and checks if they are allowed access to the door. The information governing what access is permitted is stored in Redis in the form of key/value pairs like this:

access/<door>/<card id> -> {yes/no}

If no key is found with that name then access is assumed to be denied. If a key is found and the value is 'yes' then access is granted, and if any other value is present then access is denied.

The code for this application is here: https://github.com/richardjkendall/door-access-manager

Putting it all together

The other bit I've not talked about in detail is the door strike. Normally a door locks against a static 'strike plate' and to unlock the door you use a key to retract a bolt which allows the door to move freely.

With an electric strike, the strike plate itself can move which provides a second way for the door to open - the bolt can remain extended but the strike gives way under force.

Most electric door strikes use a solenoid which when powered pulls back a pin which allows the door to open. There are two kinds: fail open and fail closed. A fail closed strike locks in the abscence of power - which is more secure, but less safe if people need to escape through the door. As my door is for an outdoor storage room, I'm okay with 'fail closed' (also know as: fail secured).

The strike plate gets its 12V power to open when the relay on my circuit board is closed, and as we know from above that relay is controlled by the access manager application.

door strike and hid reader

What next

I'm going to be doing more IoT/smart home things with the building blocks of this solution (Redis and MQTT). I'll also probably clean up my door-interface application and build a small GUI to allow me to add and remove access cards easily (right now I need to use the command line and interrogate logs).

Thanks for reading.

-- Richard, Mar 2021