In Part 1 I described the hardware for this project and how to connect it. In this part I will describe the Arduino code that runs in the LightBlue Bean.
Punch Through Design, the creators of the LightBlue Bean, provide a library that provides integration between the Arduino and the other hardware on the Bean. The first step is to get an Arduino development environment set up.
Head on over and follow the Punch Through Getting Started Guide – I’ll wait here until you’re done.
Back? Good.
Method of operation
Before we get stuck into the code, I will describe how the lock works.
Essentially, the Arduino code listens for a password. If the password is received then it energises the relay for 5 seconds. The password is sent as a message over the Bluetooth Low Energy (BLE) link from the iOS application using the following format –
<stx>password<etx>
<stx> is character-code 2 or ctrl-b and <etx> is character-code 3 or ctrl-c. The reason for these characters is to ensure the software can distinguish a valid message and recover from garbled messages.
The Arduino code sends and receives data over a serial port. In the LightBlue Bean this serial port is connected to the on-board BLE chip. Firmware in the BLE chip automatically sends data from the serial port over a BLE characteristic (and data that is received on a BLE characteristic is sent to the Arduino serial port). All of this means that the Arduino code doesn’t have to do anything special to use BLE – It is just standard serial port processing.
Once a valid message is received it is processed and the Arduino code returns a status message –
OK - The password was valid and the lock was opened No - The password was invalid and the lock was not opened Error - The message was invalid Close - This is sent unsolicited once the relay is de-energised
Use the source Luke
The code for this project is available on GitHub
If you clone the Git repository or download the zip file you will find a folder called “Arduino” and inside that “BeanLock” and inside that “BeanLock.ino” – Open this up in the Arduino editor and we can go through it.
Global variables and constants
This section creates some gobal variables and a constant
int ledTimeout=0; int LEDTIME=10; // LED time is 5 seconds (sleep for 500ms every loop) int state=0; String collectedPassword; String thePassword="OpenSesame"; |
In order to save battery, the LED on the Bean is turned off after a period. The ledTimeout and LEDTIME variables are used to control this as we will see in the main loop.
To process incoming characters, I am using a finite state machine (FSM). The state variable tracks the current state of the FSM.
collectedPassword keeps track of the password characters that are received and finally, thePassword is the password that will open the lock. I did mention that this wasn’t a particularly secure lock didn’t I?
Setup
// the setup routine runs once when you press reset: void setup() { // initialize serial communication at 57600 bits per second: Serial.begin(57600); // this makes it so that the arduino read function returns // immediatly if there are no less bytes than asked for. Serial.setTimeout(25); //Set pin 0 to Output mode pinMode(0,OUTPUT); closeLock(); } |
The setup routine runs once whenever the Arduino is reset. This simply sets up the serial port and sets pin 0 to output mode. Finally, it calls the closeLock() function to ensure that the relay is initially turned off.
Main loop
The main loop in an Arduino program executes over and over forever (or until the battery runs out or the chip is reset or it the Earth is destroyed by Vogons or something)
// the loop routine runs over and over again forever: void loop() { char buffer[64]; size_t length = 64; length = Serial.readBytes(buffer, length); if ( length > 0 ) processBytes(buffer,length); Bean.sleep(500); if (ledTimeout >0) { ledTimeout--; if (ledTimeout == 0) { // Turn off the LED after required time Bean.setLed(0,0,0); } } } |
In the main loop, I attempt to read up to 64 bytes from the serial port (which is connected to the BLE chip). This will timeout after 25 msec (This was set in the setup() function). If no bytes were available then -1 is returned.
If bytes were read then they are passed to processBytes() (see below).
If there were no bytes (or once the bytes have been processed) the Arduino is put into sleep mode for up to 500 msec. After 500 msec or if data is received then the Arduino will be woken
Once the Arduino wakes, the ledTimeout is decremented if it is greater than zero. Once it reaches zero the LED is turned off. We will see later how ledTimeout is initialised when the LED is turned on.
The state machine
The next function implements the Finite State Machine. If you haven’t come across these before they are a very useful technique for dealing with serial data streams. When dealing with keyboard input data tends to come in nice chunks because of the return key-
“Enter your name:” “Paul”<return>
“What is your favourite colour?” “Blue”<return>
Serial data streams may not deliver all of the characters at once, or data may be corrupted in flight (less of an issue with modern systems like BLE but it was a big deal back in the dial-up modem days). The state machine uses the global state variable to keep track of where we are and recover from errors gracefully.
First here is the pseudo-code for what we are trying to achieve
- State=0 – Starting state. If we receive a STX character (2) then move to state 1, otherwise stay in state 0.
- State=1 – Collecting password state. If we receive an STX character (2) clear the password buffer, report an error and stay in state 1. If we receive an ETX character (3) then report “No” and return to state 0. For any other character, add it to the password buffer. If the password buffer is the same length as the password then move to state 2 otherwise stay in state 1.
- State=2 – Verify state. If we receive an STX character (2) clear the password buffer, report an error and return to state 1. If we receive an ETX character (3) then compare the password buffer against the password. If it matches grant access and report “OK” otherwise report “No”. If any other character was received, report “Error” .Clear the collected password and return to state 0.
By breaking the process into discrete states and determining what can happen in each state you ensure that the program can’t get stuck or there is something that you don’t deal with.
Here it is in code –
// State machine to process incoming data - Format is password // STX=0x02 (^B) // ETX=0x03 (^C) void processBytes(char buffer[], size_t length) { for (int i=0;i<length;i++) { char b=buffer[i]; switch (state) { case 0: if (b==2) { state=1; collectedPassword=""; } break; case 1: if (b==2) { state=1; collectedPassword=""; error(); } else if (b==3) { denied(); state=0; } else { collectedPassword=collectedPassword+String(b); if (collectedPassword.length() == thePassword.length()) { state=2; } } break; case 2: if (b==3) { if (collectedPassword == thePassword) { ok(); pulseLock(10); closed(); state=0; } else { denied(); state=0; } } else if (b==2) { state=1; error(); } else { state=0; collectedPassword=""; } break; } } } |
Controlling the relay
There are some simple functions that send the various status messages and set some corresponding LED colours, but I will skip over those.
The final functions I will discuss are the relay control functions –
// Pulse the lock open for a specified time void pulseLock(int time) { openLock(); delay(time*1000 ); closeLock(); } // Open the lock // The relay board has an active-low input void openLock() { digitalWrite(0,LOW); Bean.setLed(0,255,0); ledTimeout=LEDTIME; } // Close the lock void closeLock() { digitalWrite(0,HIGH); Bean.setLed(255,0,0); ledTimeout=LEDTIME; } |
pulseLock(int time) turns the relay on for time seconds. It does this by simply calling the openLock() and closeLock() functions with a delay in between. delay() takes a number of milliseconds, so time is multiplied by 1000 to get seconds.
openLock() sets the digital output pin to low (The particular relay board I have has an active low control line, which works nicely with the Bean’s 3.3v circuitry – See part 1). The LED is set to green (red=0,green=255,blue=0) and ledTimeout is initialised ready to be decremented in the main loop.
closeLock() does the opposite of openLock() – setting the pin back to high and setting the LED to red.
So, there you have it. You can compile this sketch and upload it to your Bean and you will be ready for the iOS app in part 3.