Introduction: Spark Core and Android Garage Opener. Minus the Spark Cloud.

This Instructable describes a process for making a custom garage opener that you can operate from your Android phone. The end product is capable of operating the garage door and reporting door status back to the Android phone.

Normally users can program the Core through a simple web based interface. The firmware is then uploaded into your Core by the Cloud over the Internet. Of course, we will be using no such thing, and learn how to compile and upload our own firmware.

An unusual aspect of this Spark Core project is that I did not utilize the Spark Cloud for programming or communicating with the Spark, so this project includes a re-usable library that allows you to communicate directly with your Spark Core over a secure channel. This results in a much cleaner and simpler architecture.

The hardware for this project is incredibly simple, so I will get that out of the way first, and then talk about the software, which is the interesting part of this project.

Please note that all software setup instructions assume a Linux workstation. I use Ubuntu 12.04.

Step 1: Introducing the Spark Core

This is my first project with the Spark Core, and I was very impressed with this micro controller. It was very easy to get started with, b/c it provides excellent documentation (http://docs.spark.io/), out of the box WiFi connectivity, and a simple yet powerful API for reading/writing the pins (much like the Arduino library). I also found that it has an active, friendly and very helpful developer and user community (https://community.spark.io/).

Specs

The Spark Core is much more capable than an Arduino, b/c instead of the ATmega chip, it uses a 32-bit ARM Cortex™-M3 CPU.

It has 128KB of Flash memory to store your programs, whereas the Arduino only has 32KB, which means that you can make use of more powerful libraries. In terms of RAM, Spark has 20KB of SRAM, vs. Arduino's 2KB, so you can store more of your data in RAM while your program is running.

In addition to having 128KB of internal flash memory for storing the firmware, the Core also features a 2MB external SPI based flash memory chip. About 500KB of that is used for storing public/private keys and recovery images, but the remaining 1.5MB is available to the user! Since the flash memory is non-volatile, it retains the data even after turning off the power.

We will be using this Flash memory for storing our pre-computed seeds for random number generation required for implementing the encrypted communication channel.

And of course, the Core is tiny! It is roughly the same size as the Arduino Pro Mini (http://arduino.cc/en/Main/ArduinoBoardProMini).

You can find more detail about the hardware here: http://docs.spark.io/hardware/

Kickstarter Video

Step 2: Part List

Here's what you will need to build this project:

  1. The Spark Core: https://store.spark.io/. $39. Please note that the successor to the Core, the Photon is almost ready, and you'll be able to get that for $20 cheaper!
  2. A USB power supply to power the Spark Core: http://abra-electronics.com/power-supplies-transfo.... $7.78
  3. An Android device with Internet connectivity.
  4. 1.3k resistor.
  5. NPN transistor that can handle the voltage and current used by your garage opener system. I used P2N2222A: http://www.digikey.ca/product-detail/en/P2N2222AG/...
  6. A bunch of M3 mounting screws with nuts: http://abra-electronics.com/hardware/robotic-hardw.... $1.45
  7. Plastic spacers: http://www.digikey.ca/product-search/en?KeyWords=4.... $0.36
  8. A magnetic reed switch (HAA28 Velleman): http://abra-electronics.com/security-products/reed.... $7.95
  9. 0.1" Female headers: http://abra-electronics.com/robotics-embedded-elec.... $2.95
  10. 22AWG Solid Hook-Up Wire: http://abra-electronics.com/wirecable/hook-up-wire... $8.99
  11. Hot Glue gun.
  12. Industrial Strength VELCRO: http://www.amazon.ca/gp/product/B003CEXPSA/ref=pd_.... $3.76

So, assuming that you already have an Android phone, and a hot glue gun, the cost of the project is $71.88. If you get The Photon instead of the Spark Core, that becomes $51.88.

Step 3: Hardware and Electronics

The pictures above should tell you everything you need to know about putting together the hardware for this project. I am also attaching the complete Kicad project with the schematic and board design.

Interface with Garage Opener System

Most garage openers have a switch inside the garage that can be pressed to open/close the door. The specifics of this switch can vary, but in my case it was very simple. The button switch you see in the picture had 24V supplied to it on the RED wire. When the button is pressed, 24V is shorted to ground, which causes 30mA of current to flow, which signals the garage opener to activate the door.

The schematic shown above connects a transistor controlled by the Core in parallel with the button switch, which means that the door can now be opened by either the button or a signal from the Core.

Magnetic Reed Switch

We use a magnetic reed switch to tell if the door is open or closed. When the magnet piece mounted on the door aligns with the stationary reed switch, the switch closes and the Core will see a LOW signal on D0. When the door is not in closed position, the reed switch is open, so the Core sees a HIGH signal due to the use of the internal pull-up resistor inside the Core. The 40k pull-up is activated in code.

The sensors are mounted on the door using Industrial Strength Velcro, as shown in the pictures.

Making Wire Clamps

As you can see from the pictures, I used the glue gun to glue the screws to the washers, and the nuts to the back of the board. This makes it very easy to connect and disconnect the 6 wires from the board in -30C weather, while wearing huge gloves :).

The plastic spacers for the mounting screws are also quite handy for quick installation.

Making The Board

The board was designed in Kicad (professional quality, free, open-source software) and made on ProtoMat S103 available at my local Maker Space (AssentWorks). I am attaching my Kicad project to this tutorial.

Step 4: Preparing Your Spark Core

Please note that all software setup instructions assume a Linux workstation. I use Ubuntu 12.04.

Blinking Codes

The first thing you'll need to know about the Spark Core is how to read it's blinking codes. Here are the codes that you need to care about for this project. Check Spark documentation for others.

  • Blinking blue: Listening for Wi-Fi credentials
  • Blinking green: Connecting to the Wi-Fi network
  • Breathing green: Connected to the Wi-Fi network
  • Blinking yellow: DFU (Device Firmware Upgrade) mode. <-- That's the one used for programming the device

Connecting to Wi-Fi

First you must switch the Core into Listening Mode, which is indicated by blinking blue LED. If it's not in Listening Mode, switch it by holding down MODE button for 3 seconds. To completely clear all stored Wi-Fi credentials, continue to hold the MODE button for 10 seconds until the LED flashes blue quickly, signalling that all profiles have been deleted. The LED should now be flashing blue again.

Spark Core can be connected to your Wi-Fi network with Serial over USB. When you plug it into a Linux machine, the Core will be exposed as a serial device.

  • Make sure that you have permissions to access /dev/ttyACM0 (or whatever its called on your machine). I had to add myself to dialout group.
  • Connect to the serial device:
screen /dev/ttyACM0 9600
  • Press i: ("i" as in identify) to read out the Spark Core ID:
Your core id is *********************************
  • Press w: to set up your Wi-Fi SSID and password. Answer the prompts and you should be in business.
  • To exit the screen session press Ctrl-A k y

At this point the stock firmware will try to connect to the Spark Cloud, so the Core will be cycling between blinking green and blinking cyan forever. We'll need to program the Core, before it does anything useful.

Step 5: ​Install Device Firmware Upgrade Utilities

The version of dfu-util must be at least 0.7.

$ wget http://dfu-util.gnumonks.org/releases/dfu-util-0.8...
$ tar zxvf dfu-util-0.8.tar.gz
$ cd dfu-util-0.8
$ ./configure
$ make
$ sudo checkinstall
$ sudo dpkg -i dfu-util_0.8-1val_amd64.deb
$ chmod u+s /usr/local/bin/dfu-util

Step 6: Preparing the Firmware

Users can program the Core through a simple web based interface. The firmware is then uploaded into your Core by the Cloud over the Internet. Of course, we will be using no such thing, and learn how to do things locally.

There are comprehensive setup instruction available here: https://github.com/spark/firmware, but it will go something like this:

Install ARM Embedded Toolchain

$ sudo add-apt-repository ppa:terry.guo/gcc-arm-embedded
$ sudo apt-get update 
$ sudo apt-get install gcc-arm-none-eabi

Download Source Code

$ git clone https://github.com/vace117/GarageOpenerSpark.git

Build Firmware

$ cd core-firmware/build$ make

Step 7: Flashing the Firmware

  • Put your Core into the DFU mode by holding down the MODE button on the Core and then tapping on the RESET button once. Release the MODE button after you see the LED flashing yellow.
  • Make sure that we can see the device:
$ dfu-util -l
dfu-util 0.8
.... bla bla bla ....
Found DFU: [1d50:607f] ver=0200, devnum=7, cfg=1, intf=0, alt=1, name="@SPI Flash : SST25x/0x00000000/512*04Kg", serial="6D8F46734857"
Found DFU: [1d50:607f] ver=0200, devnum=7, cfg=1, intf=0, alt=0, name="@Internal Flash  /0x08000000/20*001Ka,108*001Kg", serial="6D8F46734857
 .... bla bla bla ...

Navigate to core-firmware/build, and use the following command to transfer the .bin file into the Core:

$ dfu-util -d 1d50:607f -a 0 -s 0x08005000:leave -D core-firmware.bin
.... bla bla bla ....
Opening DFU capable USB device...
ID 1d50:607f
Run-time device DFU version 011a
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuERROR, status = 10
dfuERROR, clearing status
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 011a
Device returned transfer size 1024
DfuSe interface name: "Internal Flash  "
Downloading to address = 0x08005000, size = 79900
Download	[=========================] 100%        79900 bytes
Download done.
File downloaded successfully
Transitioning to dfuMANIFEST state

The command specifies:

Step 8: Setting Up an Eclipse Workspace (OPTIONAL)

Eclipse is a good powerful IDE to use for any type of development. The Spark Core code is written in C++, so we will be using the CDT Eclipse plugin. I am using Eclipse Luna.

core-common-lib

  • Open Eclipse and Import "Existing Code as Makefile Project", selecting core-common-lib folder
  • Go to Project Properties -> C/C++ Build and set Build Directory to ${workspace_loc:/core-common-lib/build}.
  • Add the following Includes under C/C++ General -> Paths and Symbols for all languages and configs:

/usr/arm-none-eabi/include
/core-common-lib/CMSIS/Include /core-common-lib/CMSIS/Device/ST/STM32F10x/Include

  • Add the following Symbols:

USE_STDPERIPH_DRIVER
STM32F10X_MD

  • Now we need to fix compile errors where Eclipse does not find functions defined in string.h, such asmemcpy or strcmp. Go to C/C++ General -> Preprocessor Include Paths... -> Providers and click on "CDT GCC Built-in Compiler Settings"
  • Right-click on the project and select Index -> Rebuild

core-communication-lib

  • Import core-communication-lib in the same way.
  • Go to Project Properties -> C/C++ Build and set Build Directory to ${workspace_loc:/core-communication-lib/build}.
  • Add the following Includes under C/C++ General -> Paths and Symbols for all languages and configs:

/usr/arm-none-eabi/include

  • Go to C/C++ General -> Preprocessor Include Paths... -> Providers and click on: "CDT GCC Built-in Compiler Settings"
  • Right-click on the project and select Index -> Rebuild

core-firmware

  • Import core-firmware in the same way.
  • Go to Project Properties -> C/C++ Build and set Build Directory to ${workspace_loc:/core-firmware/build}.
  • Add the following Includes under C/C++ General -> Paths and Symbols for all languages and configs:

/usr/arm-none-eabi/include
/core-firmware/libraries/Serial2 /core-common-lib/CMSIS/Include /core-common-lib/CMSIS/Device/ST/STM32F10x/Include /core-common-lib/STM32F10x_StdPeriph_Driver/inc /core-common-lib/STM32_USB-FS-Device_Driver/inc /core-common-lib/CC3000_Host_Driver /core-common-lib/SPARK_Firmware_Driver/inc /core-common-lib/SPARK_Services/inc /core-communication-lib/lib/tropicssl/include /core-communication-lib/src

  • Add the following Symbols:

USE_STDPERIPH_DRIVER
STM32F10X_MD DFU_BUILD_ENABLE

  • Go to C/C++ General -> Preprocessor Include Paths... -> Providers and click on: "CDT GCC Built-in Compiler Settings"
  • Right-click on the project and select Index -> Rebuild
  • There was still one compile error left in spark_utilities.cpp. I am not sure why, but I had to fix it by casting the last parameter to (UINT32*):

gethostbyname(server_addr.domain, strnlen(server_addr.domain, 126), (UINT32*) &ip_addr);

Set up dfu-util

  • Select Run > External Tools > External Tools Configurations… and create a new configuration to execute the dfu-util command to upload your firmware with a mouse click. (See the last picture.)
  • Arguments:

-d 1d50:607f -a 0 -s 0x08005000:leave -D core-firmware.bin

Step 9: Garage Opener Code

If you followed along in the Instructable this far, you should now have your development environment loaded with the Garage Opener code ready to go.

The code should have been obtained from here: https://github.com/vace117/GarageOpenerSpark

The main file is core-firmware/src/application.cpp, and the rest of the code is found in core-firmware/libraries/garage/.

Before you compile your first firmware, there are a number of things you'll need to customize.

Configure the Test Ping Host

The Core will ping a test server of your choice every 60 seconds to make sure that the WiFi connection is still healthy. If the server cannot be reached, the WiFi connection is dropped and re-established.

Set the correct IP address in core-firmware/src/application.cpp:23.

Create a Master Key

You will need to provide a core-firmware/libraries/garage/master_key.h. I will provide an example in a later Step.

Pre-compute Random seeds, and Upload into External Flash

Again, this will be discussed later.

Other Customizations

In core-firmware/src/application.cpp:

  • Listen port. Default is 6666.
  • Frequency of test pings. Default is 60 seconds.
  • Duration of secure conversation sessions. Default is 5 seconds.

In core-firmware/libraries/garage/Garage.h:

  • Estimated door travel time. Default is 4.5 seconds.

In core-firmware/libraries/garage/spark_secure_channel/SparkRandomNumberGenerator.h:

  • The host used for gathering entropy from a network. Default is 8.8.8.8.

Step 10: Secure Communication Channel Architecture

The trickiest part of this project was the design of a secure channel that would be resistant to Replay and Man-In-The-Middle attacks. I believe that I was able to achieve this, and I implemented the solution in a way that makes it very easy to re-use in any other Spark Core project.

This step gives a high-level overview of how this library is designed and used.

SecureChannelServer

All of the crypto primitives and the session establishment protocol are implemented by the SecureChannelServer. The implementation delegates actual communication and message processing to CommunicationChannel and SecureMessageConsumer interfaces. These are the only two interfaces a user of this library must implement.

Note that the only public method this class has is loop(). It is intended to be called from the main loop() method in application.cpp, serviced by the Spark Core libraries.

The details of how this class works are in a following Step.

CommunicationChannel

Is used to implement a specific communication approach, such as TCP/IP over WiFi for example. It knows nothing about encrypting or decrypting the data. Instead, SecureChannelServer will use this interface whenever it needs to read or write encrypted data.

SecureMessageConsumer

Implementation will receive decrypted messages and will return plain-text responses back to the SecureChannelServer. It is completely agnostic of how the message was decrypted, or how it was received.

This design approach makes it very easy to develop and test the SecureChannelServer. If you look inside core-firmware/libraries/garage/tests, you'll find dummy test implementations of both interfaces, which made it possible for me to write and debug all of the crypto code w/o any need for hardware or even network.

WiFiCommunicationChannel Class

Handles the specifics of connecting the Core to WiFi, managing a TCP/IP server on port 6666, and managing client connections. This is the class that pings the test server every minute to check the connection health.

Garage Class

This is our implementation of the SecureMessageConsumer interface. It acts as the garage door controller, handling the hardware specifics of activating the switch, and reading the door sensor.

Please refer to the UML diagram for more details.

Step 11: Encryption/Decryption and Session Establishment

This step describes the specifics of how SecureChannelServer class is implemented.

Symmetric shared-key security is used. This means that the Spark Core and the client Android app must share the same secret key in order to talk to each other.

Master Key

The 128-bit Master Key must be provided by you in this file: core-firmware/libraries/garage/master_key.h. Here's an example of what that might look like:

#ifndef LIBRARIES_GARAGE_MASTER_KEY_H_
#define LIBRARIES_GARAGE_MASTER_KEY_H_
const uint32_t MASTER_KEY[4] = {0x12345678, 0x12345678, 0x12345678, 0x12345678};
#endif /* LIBRARIES_GARAGE_MASTER_KEY_H_ */

AES-128 is used for encrypting the traffic. Challenge based, timed session tokens are used to prevent Replay Attacks. Session tokens are valid for 5 seconds after the random challenge nonce is issued to the Android app.

Two crypto primitives are implemented for encrypting the traffic from Android to Spark, and vice versa.

AndroidRequest(COMMAND)

Android 1) Generate random IV_Send[16]

Android 2) Send [Message_Length[2], IV_Send[16], AES_CBC(Master_Key, IV_Send, COMMAND), <==== HMAC(Master_Key)]

Spark 1) Verify that HMAC(Master_Key, PAYLOAD) matched the received HMAC

Spark 2) Decrypt and pass COMMAND to consumer

SparkResponse(RESPONSE)

Spark 1) Generate random IV_Response[16]

Spark 2) Send [Message_Length[2], IV_Response[16], AES_CBC(Master_Key, IV_Response, RESPONSE), <==== HMAC(Master_Key)]

Android 1) Verify that HMAC(Master_Key, PAYLOAD) matched the received HMAC

Android 2) Decrypt and process RESPONSE

These two functions achieve the following:

  • The attacker cannot see the plaintext messages
  • The attacker cannot modify the encrypted traffic in any way
  • The attacker cannot tell the difference between encrypted messages, b/c same plaintext looks different every time it is encrypted

The remaining problem is that we are still vulnerable to Replay attacks, since the attacker can capture a valid encrypted message, wait until we are gone and play it back to open the garage door. To address this issue I utilized a challenge based, cryptographic nonce protocol.

Session Establishment

The following algorithm references the two functions defined above.

Android 1) AndroidRequest("NEED_CHALLENGE")

Spark 1) Use PRNG to generate random Challenge[16]

Spark 2) Calculate conversationToken == HMAC(Master_Key, Challenge[16])

Spark 3) Start 5 second timer

Spark 4) SparkResponse(Challenge[16])

Android 2) Calculate conversationToken == HMAC(Master_Key, Challenge[16])

Android 3) AndroidRequest( conversationToken, COMMAND] )

Spark 5a) Make sure that conversationToken matched the recieved one. If so, execute command, invalidate conversationToken and SparkResponse( conversationToken, DOOR_STATUS] )

Spark 5b) If timer expires, invalidate conversationToken

Android 4) Make sure conversationToken in response matched, update screen and invalidate conversationToken

This protocol is also depicted in the attached sequence diagram.

Step 12: SparkRandomNumberGenerator

As discussed in the previous Step, the Spark Core must have the ability to generate random Initialization Vectors and Challenge Nonces. This requires a PRNG (Pseudorandom number generator) algorithm. Luckily, rand48 function is available in libc.

However, a PRNG must be seeded with a random seed. It is very important for this seed to be unpredictable and non-repeating over a reasonable period of time. If the attacker can predict our seed, they can predict our random numbers, which also means that they can figure out our Challenge Nonces and Initialization Vectors.

For example, if we always use the same seed to kick off our PRNG, then all the attacker has to do is capture any encrypted message, and then force our Spark to reboot. After that the attacker simply replays the captured message, until the Spark hits the same random number as was used to construct the recorded message, and we are hacked.

The use of more than one seed does not solve the problem, b/c the same attack described above still works, if the attacker has the ability to continuously reboot the Spark as many times as there are pre-computed seeds.

This is why we must always mix our pre-computed seeds with some true source of entropy. That's easy enough on a large complex computer, but the Spark Core is too simple to have any true entropy in its own memory, so the entropy must come from an external source.

There are dedicated devices that can supply this entropy by amplifying the quantum noise in a reverse biased semiconductor junction, or dedicated security chips, but that would be overkill for this project.

Instead we can use the entropy inherent to the speed with which packets move over a network, as well as using the entropy of the time when a request is being precessed.

Algorithm

So, the algorithm used by SparkRandomNumberGenerator to generate a random number is as follows:

The entropy used for seeding the PRNG function (seed48) is mixed together from 3 different sources:

  1. rand48 is seeded with one of 65536 pre-computed 48-bit seeds, stored in External Flash. Every time the Spark reboots, the next seed is used. The seed rotation can be turned off by commenting out ROTATE_SEED in SparkRandomNumberGenerator.h:35.
  2. A specified network server is pinged 5 times (default is 8.8.8.8). Each ping time is used as a round of HMAC(Master_Key, ping_time). The first 128 bits of the resulting HMAC is our additional entropy XORed with every call to rand48. Network entropy gathering stage is what's responsible for a longish delay on the first request to the Spark Core after it boots. To disable this stage comment out PING_TEST_SERVER in SparkRandomNumberGenerator.h:34.
  3. The generated 128-bit random number is XORed with first 128 bits of HMAC(Master_key, Current_Timestamp). So the time at which the random number was requested is used for additional entropy.

All we need to do now is store the pre-computed seeds in External Flash. See next Steps.

Step 13: Using External Flash

Spark Core has 0x180000 bytes (~1.5MB) of free storage in external flash. This can be very useful for storing all kinds of resources.

For example, to upload a 2K binary file into external flash:

dfu-util -d 1d50:607f -a 1 -s 0x80000:2048 -D upload.bin

To download 2K of external flash data into a file:

dfu-util -d 1d50:607f -a 1 -s 0x80000:2048 -U download.bin 

You must always write even number of bytes!

To read the stored data from code on the Core:

uint16_t currentSeedIndex;
sFLASH_ReadBuffer((uint8_t*)&currentSeedIndex, CURRENT_SEED_INDEX_ADDRESS, sizeof(currentSeedIndex));

To write stored data:

sFLASH_EraseSector(CURRENT_SEED_INDEX_ADDRESS);
sFLASH_WriteBuffer((uint8_t*)&currentSeedIndex, CURRENT_SEED_INDEX_ADDRESS, sizeof(currentSeedIndex));

I am not sure why, but in some cases you may have to erase a sector before writing to it. The size of the erasable sectors is 4kB = 0x1000. So e.g., if you erase the sector at 0x80000, that erases everything from 0x80000 to 0x80FFF. The next sector begins at 0x81000.

Step 14: Uploading Pre-Computed Seeds

There is a small utility program provided for just this purpose. You can find it in RandomNumberGenerator/src/RandomNumberGenerator.cpp. Run this program and it will generate a file called seeds.bin. The contents of this file will need to be stored in the External Flash on the Core.

Storing The Seeds in External Flash

Upload the firmware to the Spark Core like so:

$ dfu-util -d 1d50:607f -a 1 -s 0x80000:393218 -D seeds.bin 

393218 is the size of the seeds.bin file. (65536 keys * 6 bytes each)

Step 15: Debugging the Garage Opener

The firmware is programmed to initialize the Serial library, which means that you can plug it into any computer and access the logs as soon as the Core powers up:

screen /dev/ttyACM0 9600

This will give you access to all kinds of useful logging that you will need to troubleshoot the Core.

The source code is currently configured not to wait for the user to connect over Serial, but it can be useful to turn that wait on. To do this go to core-firmware/libraries/garage/utils.h:65 and uncomment the code that makes it wait until the user presses [Enter].

You will probably want to do this at first, while becoming familiar with the project. Also, this gives you a convenient way to figure out what the IP address of the Core is on your WiFI network.

Step 16: Android App Architecture and Code

The Android app is designed with very similar principles as the Spark Core library.

All the crypto and protocol specifics are isolated into a library (yellow on the UML diagram). The AESChannelClient is responsible for all the encrypting/decrypting and session management. The secure conversation (request followed by a response) is managed by the Conversation interface. Asking for an instance of Conversation automatically performs the handshake and returns a Conversation prepared to send a secure message.

The GarageDoorController is the class that sends commands to the Core and interprets the responses.

The GarageControlActivity is the main Activity of the Android app.

Refer to the UML diagram and the code for details: https://github.com/vace117/GarageOpenerAndroid

Master Key

You must use the same Master Key you used on the Core, but for Android it is specified differently. The key must be written into a binary file and dropped into GarageOpener/assets/master.key. You can use any hex editor to do this.

Step 17: Control Scheme

The Android app connects to a TCP/IP socket exposed directly to the Internet. The available commands are:

  • NEED_CHALLENGE
  • OPEN
  • CLOSE
  • GET_STATUS

The possible responses from the Spark Core:

  • DOOR_OPEN
  • DOOR_CLOSED
  • DOOR_MOVING
  • SESSION_EXPIRED

Since the Core has access to only one door sensor, there is no way to tell if the garage door has finished moving, or if it is stopped in the open position. We can only identify the closed position. Because of this, the time it takes for the door to open or close is estimated by the Core, specified by the doorTravelTimer. The default is set to 4.5 seconds.

After an OPEN or CLOSE command is received, the Core will continue sending the DOOR_MOVING status for every GET_STATUS request, until the doorTravelTimer expires. At that point the Core will read the sensor and report either DOOR_OPEN or DOOR_CLOSED.

The Android app reacts to these status reports by starting different animations, or putting up status pictures.

Step 18: Final Result

Here's the action video for the whole project.

Step 19: Attributions for Animations Used

I used 2 animation for the Garage Opener Android app.

Progress Bar Animation

http://videohive.net/item/hi-tech-opener/5714391

Door Open/Close Animation

Taken from: Sg Siva Youtube Channel.

Video link: