All a garage door opener application really needs to do is fire off a relay when a button on a user interface is pressed. The relay then opens and closes the garage door opener circuit. Anything beyond this basic functionality is overkill!
As the project evolved it grew to encompass:
- A server based web application, with a mobile styled user-interface, coupled with a services layer that handles all the API communications with the Particle Cloud and the Photon micro controller.
- A user interface that provides an indication of whether the door is actually in the process of going up or down.
- HTTPS and ASP.Net forms authentication.
- Back end logging of activity.
- Lots of error checking routines.
- Logging of detected errors as well as unhandled exceptions.
- Server side hiding of secret information such as the token to access the Photon micro controller in the garage.
This was very much a learning project so don't expect to see textbook examples of best practices here. For the most part it was allot of trial and error experimentation until something worked.
IMO Particle did a good job of creating an ecosystem for IOT projects. Several time I tried coding a particular functionality only to later discover that it was already built into the Particle API. There is great depth to the capabilities being offered by their ecosystem and I was barely scratching the surface.
Video of the application in actionhttps://drive.google.com/file/d/0ByGCHdCN3i5HSlFUczE3cnZNcHM/view
Detection of door movement and directionWhat would be the best way to know if the door is moving? Maybe use a vibration detector and sound detector together? Motion could be detected when the motor is running and creating vibrations which could be detected by vibration sensor. The motor running would create noise (as can be heard in the video) which could also be detected by the sound detector. Together the triggering of these two sensors might be guaranteed way to know that the door was moving.
But after many hours of experimentation I found I the data being sent by the sensors was pretty close to being random noise when the door was moving. I reluctantly gave up on this idea and started looking for another way to detect motion.
Garage doors have rotating objects which could potentially be monitored by a rotation-aware sensor.
The above picture is the garage door opener with it's cover removed. There is a clutch mechanism which has shaft extending out of it. That shaft rotates because it is driven directly by the motor. If the shaft is moving clockwise the door is moving up. And counterclockwise is moving down.
I decided to focus on the rotating of this shaft as a way to detect motion. Note the 'added extension' component is explained further below.
Google searches showed that a rotary encoder can be used to detect rotation. But these devices are expensive and meant for situations where precise levels of rotation detection is needed.
Another idea out there was to use a hall sensor in combination with some neodymium magnets. With hall sensors the are fewer moving parts other than a magnet passing by a sensor. So that should make a more reliable and less error prone solution when compared to a rotary encoder.
To test out the idea of using hall sensors I made a test harness out of an small electric fan (has a rotating fan shaft) and a PVC pipe fitting.
Above shows a 7/8" hex nut being attached to the fan shaft (fan blade is removed). A tiny neodymium magnet is sticking to the side of the nut. When the fan is turned on the shaft spins the magnet past one of the three sensors in a certain order. That order can then be used to determine the direction of rotation.
Clockwise motion occurs when the sensor triggering sequence is either A->B->C, B->C->A or C->A->B. Counter clockwise is either A->C->B, C->B->A or A->C->B. C++ code checks the sequence in which the sensors are triggered to determine if the door is moving up (clockwise) or or down (counter clockwise). Revolutions Per Minute is easily determined by counting how often one of the sensors is triggered per second and multiplying by 60.
The other sensors I used for the project were normally-open reed switches. Installing these was pretty straight forward and there are plenty of examples already available on how to set these up for garage door opener applications.
Hall sensor housing (i.e. 'added extension')The three hall sensors needed to be positioned in a certain way around the rotating shaft. The problem was the shaft was too short to work with. So the fix was to bolt another bolt onto the end and extend the shaft like this:
The three sensors were positioned around the extended shaft by using a 2 1/2" to 3" PVC pipe adapter. This adapter had a nice 'shelf' that could be used as an attachment point for the hall sensors. This proved to be a somewhat difficult task as a custom wiring harness also had to be built so they could be mounted inside the PVC pipe. The tiny black rectangles shown below three-wire DuPont connectors. The hall sensors are then plugged into these connectors.
The holes for passing the wiring harness were made by heating up a screw driver tip in a gas stove flame. The hot tip was then able to melt the rectangle shaped hole into the side of the PVC. I needed to take a small nail file and smooth out the rough melt hole to get to the point where the DuPont connector could easily slide in.
Each hall sensor has a ground, voltage-in, and micro-controller pin wire. The common ground and voltage-in are shared so in total six separate wires were needed for the harness.
Building the harness was not trivial. Learning how to use a crimping tool and picking out the type of connectors you want is a whole separate topic. Google is your friend if you want to learn that skill. Shrink wrap tubing came in very handy as a way to make things much neater.
I fastened the end of the connectors to the interior 'shelf' of the PVC pipe adapter using a dab of hot glue. The three wires of each of the hall sensors were then plugged into the male three 'sockets' of the DuPont connectors and bent up so they would be triggered by the magnet as it passed by. Care had to be taken to ensure the sensors were plugging into the correct orientation to match the wiring of the harness.
The correct side of the hall sensor has to be facing the magnet and it needed to be pretty close to the magnet as it passed by. Because the sensors had fairly long lead wires (they look just like a transistor) there was opportunity to adjust the position of the sensor, by bending the wires as needed, so it would be in the correct orientation with the magnet. This allowed ability to move the sensors about 1/4" up or down (1/2" range total).
Mounting the sensor assembly on the opener
I attached two brackets to the door opener cover.
Then screwed the hall sensor assembly onto the brackets.
Note the shiny bump on the bolt at 3:30 pm position. That is the tiny neodymium that is sticking to the bolt. I wrapped a thin piece of duct tape around it for extra security to make sure it stayed attached to the bolt.
Not sure there is anything useful to pass on here but thought I'd show it anyways. You could simplify things by using a relay shield instead of building the relay mechanism as I did. But then you might not learn much about relays.
The relay components are outlined in purple. They are as follows:
- 1 the JST connector that is connected to the opener
- 2 the relay
- 3 the NPN transistor
- 4 a 220 ohm resistor
- 5 diode.
The green box in upper right is common ground.
Blue box upper left is common voltage.
Yellow box is to be ignored as it's for plugging in the sound sensor which is not being used anymore.
The big white X is to be ignored as this was for the analog pins used for the vibration detectors which is also not being used.
The red box is a step down buck converter. I have a bunch of old AC to DC power adapters lying around of varying power and am able to power the board with one of these through the use of this converter. Adjusting a screw on the buck converter steps down the higher voltage to the 5 volts needed by the photon.
Final installed assemblyThe black box underneath the board can be ignored. It's part of the original door opener electronics and it was easiest to just attach the new board on top of it.
The relay leads from the board are coming in from the bottom. When the relay trips the circuit is closed and the door either opens or closes.
Connectors are used to attach the hall sensor harness and the reed switches to the respective pins on the Photon. This makes is MUCH easier to disassemble, trouble shoot, and test the unit from my desktop PC.
C++ code on the PhotonSource Code Listing: GarageDoor.ino
A particle.function() is only capable of returning an integer. So all the information about what is going on with the garage door opener when the opener button was pressed had to be passed back in an integer.
So integer flags were needed to provide bit level information about the various statuses of the sensors and the errors:
//This enum is the backbone of the application. Results of a particle command are
//passed back with an int. Setting individual bits of this int provides the results
//back to caller as well and also keeps track of the status of the application.
//This enum needs to be kept in sync with version of the enum on the client and server
enum StatusBits
{
//Lifetime of sensor bit is untill the next check of the sensor occurs
//sensor bits 1-8 0000 0000
bitMoving = 0, //1 0001
bitDoorOpened = 1, //2 0010
bitDoorClosed = 2, //4 0100
bitOpening = 3, //8 1000
bitClosing = 4, //16 1 0000
//Errors are cleared out after every command response
//error bits 9-16 0000 0000
bitErrGlobalState = 8, //256 1 0000 0000
bitErrLastOperation = 9, //512 10 0000 0000
bitErrMoveTimeout = 10, //1024 100 0000 0000
bitErrCannotStopMove = 11, //2056 1000 0000 0000
bitErrMoveNotStart = 12, //4096 1 0000 0000 0000
bitErrWrongDirection = 13, //8192 10 0000 0000 0000
};
Some helper methods were needed to work at the bit level:
int isBitOn(int flagEnum, int bit)
{
//To check a bit, shift the number x to the right, then bitwise AND it:
return flagEnum >> bit & 1;
}
void setBitOn(int &flagEnum, int bit)
{
//Use the bitwise OR operator (|) to set a bit.
flagEnum |= 1 << bit;
}
void setBitOff(int &flagEnum, int bit)
{
//Invert the bit string with the bitwise NOT operator (~), then AND it
flagEnum &=~(1 << bit);
}
Example of usage of detecting an error. In this case it should be impossible for the door to be both opening and closing at the same time. If that ever happens then something is seriously wrong and needs to be reported.
if (isBitOn(_pvStatus, bitClosing) && isBitOn(_pvStatus, bitOpening))
{
Log.error("checkValidGlobalState: door is both closing and opening");
setBitOn(_pvStatus, bitErrGlobalState);
return;
}
Here is an example of what the UI will do if this particular condition occurs:
Many other error conditions are being checked and reported if they occur. This bit level enum needs to be defined in the C# server code, the browser JavaScript code, and in the Photon C++ code. If its changed in one place then it need to be changed in the other two which is bad but I didn't know of a way to make this centralized.
Working in this environment was not easy. There is no ability to debug and step through the C++ code as it's running in the Photon micro controller. The only way to tell what was going while the code was executing was through the use of the console and lots of print statements. In order to use the console output the Photon had to be plugged into a USB port on my machine.
I could not realistically test the app by constantly running the garage door up and down hundreds of times -- my neighbors would go nuts from the noise! So various approaches were needed to create test harnesses that would allow for console output debugging from the desktop with the Photon connected to USB with either real or simulated versions of the sensors hooked up to it.
The code needed ability to read from five different sensors (three hall sensors and two reed switches). Some pauses were needed in the execution to wait for events to happen like the door movement to begin after the relay was fired. I found that these pauses blocked the main loop which meant there was no way to continually monitor various statuses in the main loop.
In the end I settled on using both software timers mixed in with some interrupts. These loops being run by these timers did not get blocked by delays in the thread that was running the 'press button' command. This combination got the job done but I'm sure a real C++ coder could rip apart my code and put it back together in a much better way. There is sloppiness in the process I used because no decisions can be made until enough time has elapsed to allow each of the sensors to have updated it's status from it's own timer's looping logic (multiple timer loops are running at the same time). There are many opportunities for things to go wrong due to timing differences.
AuthenticationThe project uses ASP.Net web forms and *.asmx web methods. Eventually I'd like to convert to using the more modern MVC and Web API implementations but you have to start somewhere and these older frameworks are easier to work with. Web Forms also come with a relatively easy-to-implement userid/password security framework. But it also comes with a drawback of just automatically doing a bunch of things that you don't really know about or understand. It works but I don't really know how.
Server components seemed to be a requirement in order to hide secret information like: database connection strings, user ids/passwords, and keys that allow access to the Photons and the Particle Photon cloud. You don't want to just hard code these into a static html page (unless that page lives only on your secured mobile device and no where else). These settings are all hard coded in a web.config on the hosting server. I'm pretty sure a web.config can be encrypted if you want to make these secrets even more secure.
The application only needs two pages: a login page and a page that displays the user interface once you are authenticated. The main UI page then makes ajax calls to web methods on the server that then eventually run the code on the Photon to open and close the door.
Request flow: mobile device async ajax call -> web server -> Particle Cloud -> Photon
Response flow: Photon -> Particle Cloud -> web server -> ajax call completed
ASP.Net forms works well for cases like this where all the interactions start from from a password authenticated user session. But it didn't work too well when the Photon initiates the communication which is what happens when a Particle.publish() command is issued from the Photon. So to make that avenue work there needed to be a path for unsecured service method calls to be made when the Photon makes a Particle.publish() call. The methods being called are doing 'safe' work of logging data so it's not really a big deal if these methods are unsecured. I'm almost certain there is a way to secure these calls but didn't have the time to investigate.
HTTPSHow you go about putting this in place depends on your hosting provider. I used the SSL cert that was included with my hosting package. Once configured then the SSL just worked. No changes were needed in the code to enforce using the SSL.
Particle WebhooksParticle has a way to foward requests made from a Photon micro controller to any url you desire. These 'webhooks' are set up with a *.json file that indicates what url a particular Particle.publish() from your photon should go to.
Here is an example of the configuration file for making a Particle.Publish("ProdPublishDataReady",data) call. Here we see the request will be forwarded to https://www.mywebapp.us/Services.asmx/PublishDataReady. The Particle Cloud then handles relaying the request to the designated device and sending the response back from the Photon device.
{
"event" : "ProdPublishDataReady",
"url" : "https://www.mywebapp.us/Services.asmx/PublishDataReady",
"requestType" : "POST",
"json" : {
"particleVariable" : "{{particleVariable}}"
},
"mydevices" : true
}
These webhooks are fixed once defined. So if you need to do redirect to a different url for testing purposes then you either delete and recreate the same webhook or define two separate ones where one is pointing to your test url and other to your 'production' webhook. Then you update the C++ *.ino file to call that other webhook name (explained again futher below).
Debugging locallyThings got tricky when trying to get a Particle.publish() call being made on the Photon to hit a service running on my local machine.
First problem to solve was opening my local IIS to the internet. That should have been pretty easy but was not. It took many hours of head banging to figure out how modify both my router configuration and my Norton Internet Security so outside port 80 traffic would be allowed in.
After contacting the router vendor I finally figured out how to get the router piece working. The weird thing is I was expecting to that the public WAN address would go into the External IP box but it needed to be 0.0.0.0 instead.
The Norton Firewall rule to open port 80 was more obvious to configure.
The next problem to solve was re configuring the webhook so it would point to the url on my local server. I solved this by creating two separate webhooks and toggling them as needed from the Photon code. The "TestPublishingDataReady" webhook is identical to the one listed earlier above except it's pointing to my WAN address. The webhooks are in the source code.
//Photon code
const std::string PUBLISH_DATAREADY = "TestPublishDataReady";
//const std::string PUBLISH_DATAREADY = "ProdPublishDataReady";
Particle.publish(PUBLISH_DATAREADY.c_str(),logdata);
Once these modifications were in place I could step through the server code when a specific Particle.publish() call was made.
LoggingI had problems figuring out the best way to get application logging data from the Photon back to the server.
- A particle.function() can only send back an integer.
- A particle.publish() can only only send back 255 bytes.
There was a third option called a 'Particle Variable'. This allows you to define a variable in the C++ code which is then public accessible with a simple http GET. And it's is able to send back a string of up to 622 bytes. So that is what I used to send back the data to be logged. It's still not much data but it was enough to do something simple. The json needed to be as compact as possible so the variable names were just one character in length.
The Photon issues a particle.publish() to tell the server when it can retrieve a particle variable that contained the logging data. And the server would then make the call to retrieve that variable and log that data to the SQL logs. This is a long winded way to get things done and I'm hoping there is a cleaner way such as making an http request directly from the Photon using an appropriate C++ library.
For exception logging I used the Elmah package. Some tweaks were needed in web.config to get it working.
Two MS SQL table were used for the application logging. One table for logging the RPM readings as the door was opening and closing. And another table for logging all the activity as the door was and closing. The scripts for these databases are included in the source code. My hosting package came with SQL server so it was not too big a deal to configure that.
The logs could be used in a future enhancement to provide reporting on the mobile app that shows the garage door use over time using this data.
Comments