Software apps and online services
In my opinion two of the biggest features that Windows 10 has going for it is: Win 10 IoT Core and Alljoyn. As a long time .Net developer and a hardware geek I can combine my two favorite hobbies to build really cool connected things. If you haven't looked at Alljoyn yet then head on over to https://allseenalliance.org/ and read about why this communication framework can provide the backbone of your connected devices.
In this project I used the Alljoyn framework to connect two Universal Windows applications to get the current status of my garage door as well as open or close it. In addition to controlling the door I wanted to be able to measure the temperature of the garage.
There are many other projects like this but one of the things I wanted to do is measure how long in time (milliseconds) it is taking to open or close the door. I want to be able to use this information to determine if the door is in need of maintenance.
High Level Design
The design consists of a Raspberry Pi 2 that resides in the Garage of my Home connected over wifi. The Raspberry Pi is running Windows 10 IoT Core. There is also a temperature sensor connected to the Raspberry Pi via an Analog to Digital Converter over SPI. A Universal Windows Application runs on this Raspberry PI. The goal is not to have a UI on this device as it exposes it's capabilities over the Alljoyn framework.
A second Universal Windows Application is used to control the garage door as well as display the current temperature of the garage.
The following diagram shows the overall vision which includes some future capabilities.
The following video shows how the application works. In this video I do not have the hardware actually connected up to my garage door but you should be able to get the idea on how it would work without that.
Show me the Code
If you are like me and you want to just get right down to looking at the code head on over to my Github Repository for this project. I have created 2 solutions: GarageDoorDevice and GarageDoorConsumer. The GarageDoorDevice solution targets the Raspberry PI. The GarageDoorConsumer is a Universal Windows app that is designed to remotely connect to the device. If you have Visual Studio 2015 you should be able to clone the Github repository and open up the solutions, compile the code and run it. You can skip all the way down to the Wiring up the Simulated Hardware step,
Step 1 - Define the Communications
First you need to establish what the interface is between the device and the application that will be controlling and monitoring the device. With the Alljoyn framework this is done by building the Introspection XML document. There is a great article on Channel 9 that provides some awesome detail on authoring the Introspection xml. Since my device consists of a way to control the garage door as well as measuring the temperature of the garage it was real apparent to me that I needed two interfaces.
Check out the full Garage Door Introspection XML on my Github repository. There are methods defined in the XML for opening and closing the door. There are events (or signals) defined so that other applications can get notifications about the state of the garage door changing.
Check out the full Temperature Introspection XML on my Github repository. This interface definition is a lot more simpler since all it really does is allow someone to subscribe to the temperature changing.
On the temperature interface I started out with trying to use one that was already defined on the Allseen Alliance wiki in the Home Appliances and Entertainment Service Framework project. However I wanted to be sure I had a way to set the Location of my temperature sensor because eventually I wanted to be able to use this interface for multiple temperature sensors throughout my home. The Location setting would be placed on the UI so I could tell what room each of the temperature readings were coming from. The HAE project had the ability to set a Location but it was on a different namespace called SmartDevices.Common where the actual temperature sensor was on the SmartDevices.Environment namespace. I couldn't figure out how to use the proposed interface design without introducing complexity due to creating 2 interface libraries. So I decided to make a copy of the XML and create my own namespace that combined the Current Temperature as well as the Location setting. This isn't the best way to do this since the whole goal of using a framework like Alljoyn is to use existing service definitions so that you can have other third party solutions that understand your device. But I was sort on time in getting this article complete so I took the shortcut.
A Note about Security
One thing you will notice is that I chose to ignore security in this device. I was again pressed for time to complete this article and I needed to spend more time on building a solution that also is secure. Alljoyn does support securing your communications but I don't show that in this article.
Step 2 - Create your Alljoyn Interface Library via Code Generation
In this step you will use Visual Studio and the Alljoyn Studio Extension to convert your Introspection XML into a library that you can reference in a Universal Windows Application. There is another great article on Channel9 that shows you how to use the Alljoyn Studio Extension. The article goes into more detail about how to use the generated library in a Universal Windows Application which is a great read but right now we are just concerned about creating the Garage Service and Current Temperature libraries using the Introspection XML for each interface.
To do this step you must be using Visual Studio 2015 with the Alljoyn Studio Extension installed. You will also need to have a local copy of the Introspection XML from my Github Repository.
- Open up Visual Studio and on the menu select File -> New Project
- Pick Windows -> Universal -> Alljoyn App
- Call the project GarageDoor.Device
- As soon as the project is created an Add/Remove Alljoyn interface dialog box will come up.
- Select the Browse button and navigate to the GarageService.xml file
- The GarageDoor interface will show up in the available interfaces to Add
- Select the check box next the the GarageDoor interface
- In the Project Name textbox change the net.protosystem.GarageDoor to GarageDoorLibrary
- Select the Ok button
- Compile the newly create project.
At this point you have created a bare bones Universal Application and generated the code for one of the Alljoyn interfaces. Now you will generate the code for the next Alljoyn interface.
- On the VS menu select Alljoyn -> Add/Remove Interfaces
- Select the Browse button and navigate to the CurrentTemperature.xml file
- Select the check box next to the CurrentTemperature interface
- In the Project Name textbox change the long namespace name to CurrentTemperatureLibrary.
- Select the Ok button
- Compile the newly create project
One thing I found useful here is to make sure you create 2 projects that include the generated source files. One will target the ARM processor and the other will target the x86 processor. I simply created the ARM project first and then made a copy of the project file and adding x86 to the end of the file name. Create a second solution by copying the first one ( and name it GarageDoorConsumer) and change the solution to point to the x86 projects and make sure the compiler targets the x86 platform. There is probably a better way of doing this but this approach worked well for me.
Step 3 - Create the Device project for the Raspberry Pi
Well some of this step was already done for you when you created the new Alljoyn App. A C# Universal Windows project was created for you. We will simply be adding the service wrappers and hardware drivers in this step to get the application exposing the Alljoyn interface as a service that is advertised on the local network. I am not going to give you every step to take here but I will give you the basic steps you need to follow.
- Make sure you are setup to target the ARM platform for the solution
- Create a service class for each Alljoyn interface (see GarageDoor.Device\Service\GarageDoorService.cs as an example)
- Add the using statements for the namespace of the Alljoyn interface
- Implement the Alljoyn interface on the service class
- Add in any code that accesses the desired hardware on the device (see GarageDoor.Device\Driver\GarageDoorDriver.cs as an example)
- In the MainPage.cs file initialize your hardware and AllJoynBus so that everything is wired up
- Compile and test your code using the Alljoyn Explorer tool
Step 4 - Create the Consumer project
In this step we will create a consumer Universal Windows App that will connect to the Device app to interact with it. This application will use the generated libraries from step 2. I chose to do this by building an entirely separate project that references the generated libraries but I could have used the same application I created in Step 3 that simply acted as a client app when it did not detect the hardware existed on the device on startup.
- Open up the GarageDoorConsumer solution that you created in Step 2
- Make sure you are targeting the x86 platform.
- Remove the existing GarageDoor.Device C# Project from the solution as it is not needed in this solution
- Add a new C# Universal Windows Blank App project to the solution
- Make sure the Application Manifest Capabilities are setup to allow All Joyn.
- Add a reference to the Alljoyn generated projects
- Create a GarageDoorControlViewModel class and make sure you attach to the Alljoyn Bus, instantiate the Consumer proxies and instantiate the Watcher classes (see GarageDoor.Consumer\GarageDoorControlViewModel.cs for example)
- In the watcher_added callback make sure your register for any Alljoyn Signals (events)
- Make sure you add implementation details to the GarageDoorStateChangedReceived callback to set properties on the View Model
- Create your view (see GarageDoor.Consumer\GarageDoorControl.xaml for example)
- Modify your MainPage.xaml to include the GarageDoorControl user control
- Create the same View Model/View pairs for the Temperature Control
- Modify your MainPage.xaml to include the TemperatureControl user control
At this point you should have the code in place to consume the Alljoyn services and interact with them. But you need to wire up the hardware to see anything work.
Step 5 - Wiring up the simulated hardware
The wiring we are going to do here is on a breadboard. You will be simulating the garage door sensors using a couple of push button switches. You can also simulate the garage door relay by using an LED but if you have the relay then lets use it in this step as well. After you are done with this step you should have a fully functional project without actually connecting it to the garage.
Follow the Fritzing wiring diagram to connect all the components.
Step 6 - Test the software with the breadboard hardware
This is were the software actually starts to work with the sensors, relay board and the consumer UI application. Open up the GarageDoor.Device solution and make sure it is set to target the ARM platform and to run on the Remote Machine. Go ahead and run the application. Open up the GarageDoor.Consumer solution and run it as well. The Consumer application should automatically find the services running on the Raspberry Pi and start to read the current temperature. Go ahead and play with the garage door sensors to verify that the UI is updating as the garage door changes state. If the garage door is closed you should be able to press the Open button on the UI and the relay will kick in and release in 1.5 seconds. If the garage door is opened you should be able to press the Close button and the relay will kick in and release 1.5 seconds later.
Step 7 - Wiring up the real Hardware
Once you get past the simulated hardware you are going to be itching to get the real hardware hooked up. I will cover the garage door sensors here but some future articles will have to cover actually creating a circuit board and mounting the Raspberry Pi.
In order for me to determine where to mount the magnetic switches I first closed the door all the way. Then I looked for a place on the door to mount the magnet in such a way that mounting the magnetic switch would not be obstructed by any door hardware. I did a temporary mount of the lower magnet first because I needed to open the garage door and determine if the upper magnetic switch could also be mounted without being obstructed by any door hardware. Once I determined the magnet was in the correct position I then fastened it to the door with some sheet metal screws.
Next I mounted the switches to the wall and the garage door rail. I had to create some wood shims to get the switch positioned close enough to the magnet so it would actuate the switch reliably. Instead of using screws to mount the switch on the rail I used Gorilla Glue.
The relay needed to be connected to the garage door control unit push button switch because it was the easiest place to tap into the garage door without impacting the original capabilities of the system. I simply wired the relay Normal Open contacts in parallel to the push button switch on the control unit.
This was a fun project to get my feet wet with Alljoyn, Raspberry Pi 2 and Windows 10 IoT Core. But I had many more goals I wanted to reach before it was time to publish this article. I had ideas on gathering statistics about how the garage was functioning and using Azure machine learning to predict when the door needs some maintenance. However I rapidly ran out of time and simply could not complete all the goals.
I think it would be fairly easy to create a Garage Door Statistics service that simply would listen to the existing services and aggregate the data into a meaningful way. The power of building a service oriented solution is that the existing services would not have to change in order to extend the system to provide more features. This Door Statistics service could also send the data up to Windows Azure for further processing and number crunching.