I get locked out of my room a lot. Now I can hardly pay for college, I don't want to deal with the spare key charges my lovely university imposes on me. It was my 7th or 8th time getting locked out, I was waiting outside my dorm room window waiting for passersby to leave so I could quickly scurry in. It was in that moment of realization, "what the hell am I doing breaking into my room for the 8th time" that led me to seek out a solution. The solution was obvious, buy a smart-lock. I guess I forgot how expensive my tuition payments were, because for a second, I actually thought I could afford the $200+ price tag. I begrudgingly snapped back to reality and figured it wouldn't be worth it at that price point. Then I thought, why not just make one myself, I've built everything from high precision robotic arms to facial recognition deterrence systems (see more on that here).

I ordered a cheap HC-06 Bluetooth module off of Amazon and began tinkering around with the mobile application. Unfortunately, my finals were coming up soon and I tend to favor personal projects over studying, so the project fell by the wayside. At least it did for a couple months until I attended a startup competition, "Startup Weekend Maui".

Startup weekend is a sort of business competition where teams form and compete to come up with the best product or service. I pitched the idea for my cheap smart-lock, which I then coined "Doorman". It was a great event, and I got to work with some very interesting people. I treated the competition more a hackathon than anything, which was awesome from a technical standpoint, not so much from a "win the competition" standpoint. Anyway, I stayed up a couple of nights in a row developing the technology and got a prototype working. It functions surprisingly well, with a complete production to end user workflow. It's at a point where I feel it can be sold with a couple of hardware improvements. I figured an explanation of the inner workings would make for an interesting read.

The Hardware

In all transparency, I'm a software guy who can do hardware, not the other way around. That being said, I'm going to cover hardware first because it's fairly simple (relative to the software at least) and I just want to get it out of the way. I threw together this dumbed down schematic to help with my explination.

There are 3 major components of the system:

  • ATMEGA 328p Microcontroller

  • HC-06 Bluetooth Module

  • High tourque servo

The ATMEGA 328p acts as a center of communication for the system. It processes data sent by the HC-06 bluetooth module and issues instructions for the servo accordingly. I'll cover the code that's written onto the microcontroller in the software portion of this article. The HC-06 module pairs with the user's phone through a secure bluetooth connection. Upon receiving data, it sends it to the microcontroller for further processing. If the encrypted information is verified to be from a valid copy of the Doorman app, it instructs the servo to turn to either 0° or 90°, unlocking the door.

It should be noted that data is only transmitted one way, from the app to the bluetooth module, not vice versa. However, 2 way communication is very simple to implement, I had to hack most of this thing together in a weekend, so I was on a bit of a time crunch. I even went as far as to add a voltage divider between the TX pin of the microcontroller and RX pin of the HC-06 in the case I wanted 2 way communication. You need to add this because the bluetooth module communicates on 3.3v while the ATMEGA 328p communicates on 5v, meaning that the microcontroller can fry the HC-06 by sending it data (without a voltage divider).

The Software

There are 2 major pieces of software in the system: the mobile app and microcontroller instructions. I wrote the mobile app in C# with the Xamarin framework, which is a very powerful cross platform app building tool (However, I was only able to build it using native Android API calls, because I only have an Android device to test on). And I wrote the microcontroller instructions in C with the Arduino compiler. I was thinking about using ATMEL studio/ compiler instead, but the program's simplicity meant it didn't need access to any lower level instructions.

The heart of the microcontroller instructions is the override of its serialEvent function:

void serialEvent() {
    byte inputData[6];
    Serial.readBytes(inputData, 6);

    if(getKey(inputData, 6) == "some arbitrary key for lock"){
        servo.write(90);
    }
    if(getKey(inputData, 6) == "some arbitrary key for unlock"){ 
        servo.write(0);  
    }
}

As I mentioned, the code written onto the ATMEGA 328p is very simple. On a serial event, we write the next 6 bytes of data into a byte array "inputData". We then compare the return value of the getKey function passed the aforementioned byte array against some arbitrary key values. The getKey function decrypts the data and parses it to a string. If these keys match one of our two pre-set keys, the microcontroller tells the servo to turn to either 0° or 90°, closing or opening the door respectively.

Now for the exciting part, the app itself. I actually managed to flesh out a proper workflow for the app. Upon receiving their Doorman, users would download the app and be taken though a simple setup process. Each Doorman would be packaged with a QR code containing the randomly generated key values on the microcontroller. The user would scan this code with their phone, it would then be encrypted and stored for continued use.

For my QR scanner, I'm using the ZXing Mobile Barcode Scanner NuGet package. It's very easy to use, a simple call to the MobileBarcodeScanner.Scan method will open a scanning screen with the device's camera. If a QR code is detected, it is read and the data is returned through the .Scan method. I take the QR's information, encrypt it, and store it for later use.

Now we can finally start searching for the Doorman device. Using a call to BluetoothAdapter's StartDiscovery method, the user's phone begins searching for nearby Bluetooth devices. If one is found, a BluetoothDevice.ActionFound broadcast is fired. In order to receive/ process this, we have to create and register a BroadcastReceiver:

public class DeviceFoundReceiver : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {

            if (intent.Action == BluetoothDevice.ActionFound)
            {
                BluetoothDevice device = intent.GetParcelableExtra(BluetoothDevice.ExtraDevice) as BluetoothDevice;

                if (device.Name == Security.getCred().Split(':')[0])
                {
                    Transmit_Service.intendedDevice = device;
                    Transmit_Service.adapter.CancelDiscovery();
                    Transmit_Service.intendedDevice.FetchUuidsWithSdp();
                }
            }
        }
    }

Overriding the OnReceive method of BroadcastReceiver, we perform a check to verify that the detected BT device is the Doorman of the QR code we scanned earlier by parsing out the encrypted data stored on the user's device:

if (device.Name == Security.getCred().Split(':')[0])

We then undergo a very similar process in getting the device's UUID, which is required for Bluettoth pairing. Using the instance of BluetoothDevice that represents the Doorman, we can call BluetoothDevice's FetchUuidsWithSdp method. This will fire a UUID found broadcast, which can be processed by a UUIDFoundReceiver and stored. I parse the intent of the broadcastReceiver as follows, using the first index of the UUIDs returned.

intent.GetParcelableArrayExtra(BluetoothDevice.ExtraUuid))[0]

After getting/saving all of this "prerequisite" data we can start the connection process. Because connecting to a Bluetooth socket is a blocking call(and there's no asynchronous alternative), it's best to perform it on another thread. To accomplish this, we need to set up a connection class.

public class Connection
    {
        public BluetoothSocket socket = null;
        private Context context;

        public Connection(Context context, BluetoothDevice inDevice)
        {
            this.context = context;

            try
            {
                socket = inDevice.CreateRfcommSocketToServiceRecord(UUID.FromString(Transmit_Service.internalSettings.GetString("deviceUUID", "NO ENTRY")));
                Transmit_Service.connectThread = this;
                ((MainActivity)context).runConnection();
            }
            catch (System.Exception e)
            {
                Console.WriteLine(e.Message);
            }  
        }

        public void run()
        {
            System.Threading.Thread runConnection = new System.Threading.Thread(connectToDevice);
            runConnection.Start();
        }

        private void connectToDevice()
        {
            Transmit_Service.adapter.CancelDiscovery();

            try
            {
                socket.Connect();
                Application.SynchronizationContext.Post(_ => { Transmit_Service.main.connetionSuccess(); }, null);
            }
            catch (System.Exception connectionError)
            {
                Application.SynchronizationContext.Post(_ => { Transmit_Service.main.connectionFail(); }, null);
                Console.WriteLine("Connection Fail:" + connectionError.Message);
                try
                {
                    socket.Close();
                }
                catch (IOException closeError)
                {
                    Log.Error("Connection", closeError.Message);
                }
            }
        }
    }

Pointing out the important parts, a BluetoothSocket is created in the class's constructor using

BluetoothDevice.CreateRfcommSocketToServiceRecord

and passing it the UUID we stored earlier. If the socket is successfully established, then a call to the Main app context is made to run the connectToDevice method in a different thread.

After BluetoothSocket.Connect succeeds, a very interesting line is executed:

Application.SynchronizationContext.Post(_ => { Transmit_Service.main.connetionSuccess(); }, null);

So why would we use this instead of just calling back to the main app thread? Well for some reason, in Xamarin, callbacks cannot be made from threads other than the graphic handling thread (ie main activity thread) to the graphics handling thread. This line circumvents that limitation, calling on connetionSuccess in the main activity thread.

Congrats, we've established a fully qualified connection with the Bluetooth device and can now write/read data to/from it respectively. Let's abstract away a class that allows us to write:

public class BTCommService
    {
        private readonly BluetoothSocket socket;
        private readonly Stream outStream;

        public BTCommService(BluetoothSocket inSocket)
        {
            socket = inSocket;
            Stream tempStream = null;

            try
            {
                tempStream = socket.OutputStream;
            }
            catch (System.Exception e)
            {
                Transmit_Service.main.connectionFail();
            }

            outStream = tempStream;
        }

        public void write(byte[] data)
        {
            try
            {
                outStream.Write(data, 0, data.Length);
            }
            catch (IOException e)
            {
                Console.WriteLine("Output Stream Initialization", e.Message);
            }
        }
    }

This class simply initializes an outgoing stream using the BluetoothSocket's OutputStream property. The write method writes to that outgoing stream, sending data to the Doorman device.

That covers the barebones components of the Doorman system. I hope you enjoyed reading.

-Mr