How to implement a door unlocking system with Raspberry Pi, iPhone, and service in the cloud

Dear reader, I assume that you are interested in all the tech news. One of the most interesting in recent times is those that combine hardware and software — often classified as IoT (Internet of Things).

Observing the market, you could have noticed that the so-called Smart Home sector is gaining strength and serious players on the market are introducing more and more “smart” devices as well as offer more and more advanced systems integrating these devices to work in a homogeneous environment — some of them can work with integration platforms from Apple (Home Kit with assistant Siri), Google (Google Home with voice assistant), Amazon (Smart Home with Alex voice assistant) and Microsoft (along with Cortana voice assistant).

I suppose you, like me, like devices that make your everyday life easier and would gladly equip you with your home. However, it is not always possible due to the high price or they not always fully meet your expectations.

There is an alternative solution that can give you a lot of fun, new knowledge, and finally a practical solution to the problem you may face on a daily basis.

That’s how it was with me — I’m interested in IoT devices, I like programming and I wanted to learn more about the hardware-software combination and finally, I needed to improve some daily activities — there was no other option — “smart” system needed to be built 🙂

The solution that I will describe has been created to facilitate one everyday activity: opening the gate to a multi-family building to its residents (including myself).

Perhaps you are wondering what’s the point!? Maybe you think that it would be a good idea to improve this activity, but there are already many products out there and there is no reason for duplicating them.

If you are interested in how I decided to solve this problem, let me take you through a typical scenario where a person enters a multi-family building:

A person holds the key to the gate scenario:

Pulls out the key (e.g. from the pocket) Unlocks the lock with the key Person gets in

A person has an access code to the intercom scenario:

Types many numbers to unlock the lock (what if it’s unclear what’s the combination? :)) Intercom unlocks the electro lock Person gets in

This is not an extremely complicated activity and it may not make sense to optimize it, but if you ever tried to enter the building with your hands holding shopping bags, or your key to the gate was tucked away and it took a long time to take it out, you would certainly be happy with the solution, which would allow you to have the door open to the building at the touch of a button! Without looking for a key, without pressing a long sequence on the numbers on intercom’s keypad — only the iPhone in your pocket and one button on the door and you are in!

So, let's do it!

In order to achieve the intended goal, we will need a solution that will automatically recognize our presence nearby the entrance gate and after pressing the button on the door, the system will verify our permissions, and then (if we are allowed to pass the door) open the electro lock and thus release the door. Sounds simple, right?

The architecture of our system will be as simple as possible — I want to share with you a solution that is easy to implement and at the same time with the potential for further development according to your extra needs.

Most of the door lock control systems for buildings work in the sequence:

System listens to the presence of a person → Person near by the door (with smartphone) → System detects presence → Person launches the application → Person unlocks the door via the app → System verifies permissions → System unlocks the door

Our solution:

System listens to the presence of a person → Person near by the door (with iPhone) → System detects presence → Person presses the gate opening button on the door → System verifies the permissions → System unlocks the door

Please, notice how comfortable it is. No waisting time for getting the phone, launching the app, and interacting with the app to open the door.

It’s as simple as one button click.

How safe is our solution?

Please, keep in mind that the level of security against unauthorized use does not have to be very high here — the purpose is to prevent people who should not cross the building’s gate, but it won’t be a barrier for a person who previously possesses an iPhone with granted access to the gate. That’s why our solution is suitable for managing access to a building, but it should not be used to control access to housing.

Here, we focus on the convenience of use with a reasonable level of security.

Prerequisites:

The gate to the building with an electric lock (with access to a key and/or intercom)

Internet access for the gateway controller (e.g. WiFi in range)

A computer with macOS operating system

Parts of our system:

Gate controller (Raspberry Pi)

User’s smartphone (iPhone)

Cloud service (backend application deployed on Heroku)

Used communication channels within the system:

HTTPS

Bluetooth Low Energy (BLE 4.0)

The diagram below shows the principle of the entire system. It will allow you to familiarise yourself in detail with the role of each piece of the system, as well as with the interactions between them.

What do we need to assemble our IoT solution?

Collect the following items:

Raspberry Pi Zero W microcomputer (I’ll use also a short name: “RPi”)

Thanks to this device, we will have, apart from an advanced controller with the Linux OS, also a WiFi and BLE 4 module, without the need to attach additional modules

microSD card (at least 16GB) with an adapter for SD card reader

iPhone 5 or newer with iOS 10.3 (or newer)

Power supply with micro USB connector to drive the Raspberry Pi

Breadboard

2 LED diodes (green and red)

Relay module (e.g. SRD-05VDC-SL-C)

Push-button

Resistors: 2 × 300Ω and 1 × 10kΩ

Wires

How to connect all elements with each other?

Here is the recipe:

Note: Please, take into account that the relay module you have chosen may differ from the one on the diagram above, so connect according to specification.

This is how my physical implementation of the above scheme looks like:

As we already know what and how to connect with each other, it is time to prepare individual components of the system.

Configuration time

As first we will prepare a gateway controller — Raspberry Pi together with subassemblies.

To “be alive”, Raspberry Pi needs an operating system, and in our case, it will be Raspbian Stretch Light.

Once we have Raspberry Pi prepared and connected to the WiFi network (yes, that's important ), we can take care of preparing the environment for our program, which will be responsible for several key things:

communication with iPhone (via BLE 4.0)

generating a token and sending it to the service in the cloud (via HTTPS)

validation of the token transferred from the iPhone

opening the electro lock of the main gate

To handle the above I’ve prepared a program written in JavaScript that will be run within the Node.js environment. Later on, I will explain the more important parts of the code, but now we will continue the configuration thus we still have a few things to be installed.

Node.js installation

Log in to RPi via SSH (as user pi) and execute the following commands:

$ sudo apt-get update $ sudo apt-get dist-upgrade $ curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash - $ sudo apt-get install -y nodejs

The above sequence guarantees the latest versions of the packages, and then we install the latest version of Node.js.

To check whether the operation was successful and what Node.js version we have, run the command:

$ node --version

The next step is to install the Node.js libraries that are used in the application and these are:

UUID generator — generates an authorization token $ npm install uuid

Cache — stores generated tokens $ npm install node-cache

Console Log Formatter — makes log messages more developer-friendly

$ npm install console-stamp

$ npm install console-stamp GPIO Pins Controller — lets us read and write signals from/to RPi board pins $ npm install rpi-gpio

Bluetooth Low Energy — handles all BLE communication

Due to the fact that the installation is a bit more complicated, please follow the instruction given here

Due to the fact that the installation is a bit more complicated, please follow the instruction given here GPIO Button Event Emitter — handles buttons’ events $ npm install rpi-gpio-buttons

Request — supports HTTPS operations $ npm install request

Now we can install our program on RPi.

To do this, log in to RPi via SSH and create a folder:

$ mkdir /home/pi/gateguard

Download the ZIP-ed source code from GitHub and unpack it, and then move *.js files to the newly created folder:

$ wget -O gateguard-rpi.zip https://github.com/inFullMobile/gateguard-rpi/archive/master.zip

$ unzip gateguard-rpi.zip

$ cp -r gateguard-rpi-master/* /home/pi/gateguard/

We still have one more thing to configure, but we can’t do it until the app in the cloud is setup — it is about a generated URL address of our application, which will be included in the code on PRi, so that RPi can connect to it in the cloud.

Raspberry Pi is almost ready… so it’s time to launch another element of our system — a service in the cloud, which will be responsible for coordinating communication between the iPhone and RPi.

Two more elements of the system require an Xcode environment (preferably the newest one, currently Xcode 9.3) and if you do not have it yet, please install it from the Apple AppStore store (https://itunes.apple.com/pl/app/xcode/id497799835?mt = 12), then launch and complete the installation.

The cloud app will be deployed on the Heroku platform, where we can host our application for free for development and education purposes.

As the first step, I suggest setting up an account on the Heroku platform, within which our application will be deployed and maintained.

Go to heroku.com and create an account (if you do not have one already).

Then we will need the Heroku Command Line Interface (aka Heroku CLI) installed on the computer. We’ll use it to deploy the application.

To install Heroku CLI, follow the instruction: Heroku CLI Installation.

As the application is written with the Vapor framework (Swift on board), please install Vapor Tool Box which will support the app: Vapor Tool Box Installation.

In the next step, download the application code from the repository. To do this efficiently, use the Git client:

$ git clone https://github.com/inFullMobile/gateguard-cloud.git

Let’s prepare our project for deployment with Heroku CLI commands:

$ cd gateguardcloud

$ rm -rf .git #it removes current git version control files $ heroku login #once asked, provide your username (email) and password

#create new git local repository

$ git init

$ git add .

$ git commit -m “Initial commit”

We can now deploy our application to Heroku servers:

$ vapor heroku init

as a result, you will be asked some questions and you may answer as I did:

Would you like to provide a custom Heroku app name?

y/n> n

Would you like to deploy to a region other than the US?

y/n> y

Region code (us/eu):

> eu

https://pacific-lowlands-45092.herokuapp.com/ | https://git.heroku.com/pacific-lowlands-45092.git

Would you like to provide a custom Heroku buildpack?

y/n> n

Setting buildpack...

Are you using a custom Executable name?

y/n> n

Setting procfile...

Committing procfile...

Would you like to push to Heroku now?

y/n> y

This may take a while...

Building on Heroku ... ~5-10 minutes [ • ]

and finally

Building on Heroku ... ~5-10 minutes [Done]

Spinning up dynos [Done]

Visit https://dashboard.heroku.com/apps/

App is live on Heroku, visit

https://pacific-lowlands-45092.herokuapp.com/ | https://git.heroku.com/pacific-lowlands-45092.git

Remember generated domain for your cloud app for later use (in my case it is https://pacific-lowlands-45092.herokuapp.com)

It is a good moment to return to the code on RPi and configure the correct URL address of our service in the cloud. To do this, reconnect to RPi via SSH and open the file /home/pi/gateguard/cloud_module.js for editing then enter the correct address of the service on Heroku (code: line 35). In my case it looks like this:

uri: ‘https://pacific-lowlands-45092.herokuapp.com/register-token'

We need two more unique identifiers for Bluetooth communication, and you can get them by doing a 2× command:

$ uuidgen

In my case, it has been generated UUIDs:

93384AB6–9EB1–4AF2–90FB-F88ABB6F79AF

4E98BE1C-F8D9–46AD-9D08-C0AAA7DFEE7A

The first on them will be used on Raspberry Pi as BLE peripheral’s service identifier, the second will be assigned to the peripheral’s characteristic.

Assign your generated UUIDs to service and characteristics in source file /home/pi/gateguard/shared_module.js respectively:

this.bleServiceUUID = '93384AB6-9EB1-4AF2-90FB-F88ABB6F79AF' this.bleCharacteristicUUID = '4E98BE1C-F8D9-46AD-9D08-C0AAA7DFEE7A'

Are you ready to launch the program on PRi? 10… 9… 8…

$ nohup sudo node /home/pi/gateguard/gateguard.js > /home/pi/gateguard/gateguard.log &

where the nohup command will guarantee the work of the program in the background as we disconnect from the RPi, while sudo is necessary for our program to work on permissions allowing it to access BLE and IO ports of our microcomputer. All logs thrown by our program will go to gateguard.log file.

Time for the second element of the system — an application for the iPhone.

In this case, you just need to download the project source files from the repository:

$ git clone https://github.com/inFullMobile/gateguard-ios.git

and open the project in Xcode.

Before running on the iPhone, set the correct URL address of the app in the cloud, what can be done in the project’s source file HttpClient.swift — change the address assigned to the constant:

// That's my URL address! Use yours ;) static let gateGuardHost = URL(string: "https://pacific-lowlands-45092.herokuapp.com")!

Now configure the correct UUIDs for BLE service and characteristic in the BLEService.swift file (remember to assign appropriate UUIDs to service and characteristics — the same as in sources of PRi app):

static var serviceUUID: CBUUID {

return CBUUID(string: "93384AB6-9EB1-4AF2-90FB-F88ABB6F79AF")

}

static var newTokenNotificationCharacteristicUUID: CBUUID {

return CBUUID(string: "4E98BE1C-F8D9-46AD-9D08-C0AAA7DFEE7A")

}

Ensure that Bluetooth on iPhone is ON!

Now you can launch the application on your phone.

Congratulations! You did it! Our GateGuard should now work as expected — test it with alternately Bluetooth switched ON and OFF on the phone, and you will observe the result of the action in the form of flashing or glowing LEDs and the operation of the relay, which connected to the electro lock will unlock the gate to the building.

For your information, I’ll explain the LEDs behavior:

The green LED blinks — the gate opening button has been pressed, the iPhone with the Gate Guard application is within RPi BLE range and the authorization is in progress

The green LED is on — the door’s electro lock is open

The red LED blinks — the gate opening button has been pressed but RPi has not identified the iPhone with the Gate Guard application running

The red LED is on — the user hasn’t passed the authorization successfully (the token from the iPhone is invalid) and the gate will not be opened

Please note that there is no authentication step in the iPhone’s application. Due to the simplicity of the solution, I’ve skipped the authentication process, but there is nothing to prevent you from adding a login screen that will protect the application from unauthorized use.

A bit about code itself

If you are interested in how communication between RPi, iPhone, and application in the cloud has been resolved, stay with me, and I will explain the most important pieces of the code right now.

If you are new to the BLE subject, I recommend a very good introduction made by Mikołaj Chmielewski in the article Introduction to Bluetooth LE on iOS: Mi Band 2 Case Study.

What is happening on Raspberry Pi?

Our device is responsible for the following:

acting as a BLE peripheral for iPhone connected to it

generating a new token

transferring token to the cloud app

validation of a token transferred from the iPhone

electro lock control

Let’s go through the source code to see what’s happening there.

Following the above bullet points, acting as a BLE peripheral for iPhone connected to it is handled by the code:

onSubscribe(maxValueSize, callback) { console.log('BLE characteristic subscribed') shared.subscribedToCharacteristic = true this.valueDidChangeCallback = callback }

Generation of authorization token is done by the code:

const tokenId = Math.floor(Math.random() * MAX_TOKEN_ID) const token = uuid()

Transferring token to the cloud app is done this way:

registerTokenInCloud(tokenId, token, completionCallback) { console.log('register token in cloud service') if (!this.requestInProgress) { console.log('http request') const _this = this this.requestInProgress = true request(this._requestOptions(tokenId, token), function(error, response, body) { console.log('http response') _this.requestInProgress = false if (!error && response.statusCode == 200) { console.log('New token registered on cloud service with success') completionCallback(true) } else { console.error('Error while registering token in cloud service: ' + error) completionCallback(false) } }) } } _requestOptions(tokenId, token) { return { uri: 'https://gateguard.herokuapp.com/register-token', method: 'POST', json: { "id": tokenId, "token": token } } }

Another step is the validation of the token:

onWriteRequest(data, offset, withoutResponse, callback) { console.log('Authorization request from mobile phone') this.greenLedManager.off() var dataParts = data.toString().split('|') const tokenId = dataParts[0] const token = dataParts[1] var cachedToken = null try { cachedToken = shared.cache.get(tokenId, true) if (token.toUpperCase() == cachedToken.toUpperCase()) { console.log('Token is valid! Opening the gate...') this.greenLedManager.on(led.LEDModeEnum.solid, shared.ELECTROLOCK_ON_DURATION) this.relayManager.on(shared.ELECTROLOCK_ON_DURATION) } else { console.log('Token is invalid - access to the gate denied!') this.redLedManager.on(led.LEDModeEnum.solid, shared.ELECTROLOCK_ON_DURATION) } callback(0) } catch (err) { console.log('Error: ' + err) callback(1) } }

Electro lock controlling is implemented in relay_module.js.

Opening the gate is happening here:

this.relayManager.on(shared.ELECTROLOCK_ON_DURATION)

What about iPhone’s role?

The app mainly works in background and

is looking instantly for particular UUID which represents RPi service/characteristic

gets requested token (request comes from RPi) from cloud service

sends token back to RPi for further authorization procedure

Instant looking around for familiar Raspberry Pi looks like this:

private func scanForPeripheral() { guard self.isElectronicKeyActive else { return } self.centralManager.scanForPeripherals(withServices: [CBUUID.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(booleanLiteral: true)]) }

and successively

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard error == nil else { return } guard let service = peripheral.services?.filter({ $0.uuid == CBUUID.serviceUUID }).first else { return } peripheral.discoverCharacteristics([CBUUID.newTokenNotificationCharacteristicUUID], for: service) } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { guard let characteristic = service.characteristics?.filter({ $0.uuid == CBUUID.newTokenNotificationCharacteristicUUID }).first else { return } self.storedCharacteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) }

Once the peripheral device is connected and the gate’s button pressed, it generates new authorization token (unknown for iPhone’s app) and requests for it. The token is sent to RPi app right after being taken from cloud service.

self.bleService.tokenDidRequestCallback = { [weak tokenService, weak bleService] (_ tokenId: Int) in tokenService?.getToken(with: tokenId) { (_ result) in switch result { case .success(let token): bleService?.respond(withToken: token) case .error(let error): let errorMessage = String(describing: error) print("Error: \(errorMessage)") } } }

There is also a token exchange platform — service in the cloud.

The backend application is written in Swift with the use of the Vapor framework and hosted on Heroku servers. Simple service which is responsible for two things:

storing newly generated authorization token (for a limited time only)

answering with token while being asked for it with tokenID

The first responsibility is handled this way:

post("register-token") { req in guard let id = req.json?["id"]?.int, let token = req.json?["token"]?.string else { return Response(status: .badRequest) } try self.cache.set("\(id)", token, expiration: Date(timeIntervalSinceNow: Constants.tokenDuration)) return Response(status: .ok) }

The second is implemented this way:

get("token") { req in guard let id = req.query?["id"]?.int else { return Response(status: .badRequest) } var json = JSON() guard let token = try self.cache.get("\(id)") else { return Response(status: .noContent) } try json.set("id", id) try json.set("token", token) return json }

As you can see, all pieces of the system talk to each other via BLE and HTTPS. Details of implementation you can always find in provided repositories.

That was huge fun for me to build that system and I hope you may find it interesting too.