I've been looking for a reason to build something IOT for a while but never had a good enough reason. A number of things have blocked me, namely time, and a lack of electronics knowledge. Fortunately working with people who have the experience makes it easier and all I needed was an idea.
*Enter stage left, my Grandma*. Irene now lives on her own and generally gets visitors but she has a habit of ignoring the phone on a morning. This worries all involved and usually results in each sibling calling the other to go check she is up and about. I quickly realised we could help with this problem with a little bit of technology.
Both myself and guys in the office threw the idea around of a PIR connected to Raspberry Pi which seems like a good idea, apart from the cost. I wanted to keep the cost below the £10 mark and have the bare minimum hardware to run it.
After scouring a few different blog posts somebody forwarded me one on the nodeMCU (ESP2866). At this point, the idea of plugging a board in and programming it is a little abstract but I ordered the parts with the faith that I will be able to make it work...
Part | Cost |
---|---|
NodeMCU (ESP 2866 12E) | £4.10 |
HC-5201 (PIR) | £1.70 |
DHT11 (Humidity and Temp) | £1.54 |
What makes the nodeMCU useful and accessible is that it has an onboard WiFi shield for under £5.00. I wan't quite sure what to expect in terms of getting code to run on it but you can use the Arduino IDE so I managed to fumble my way through it. My biggest blocker in getting started was being a Mac User, and the chipsets for nodeMCU being different depending on what module version you are running.
Getting the Arduino IDE set up
- You will need to install the Arduino Package for ESP2866
- *On Mac you will require the UART driver, this will be different depending on which version of the NodeMCU you are using.
- Install the DHT11 Package
- Connect your components together (see diagram)
- Start the IDE
*Minimum OSX El Capitan
https://github.com/adrianmihalko/ch340g-ch34g-ch34x-mac-os-x-driver
Note that if you have the v3 board, you will be lucky to get it on the breadboard. I actually settled for the row of pins that I use the most to be on the breadboard and have the rest hanging off the edge.
Writing Editing code in Arduino...
I am no stranger to code so this should be the easy bit, turns out that is pretty much correct. Apart from a few syntax issues I am now in a relatively familiar environment but unsure where to start. A helpful colleague shows the the example sketches menu which really blew me away and started me off on the right path.
This just popped open a sketch which output a ton of information in respect to the DHT11 (which we added to the PIR project Just for Fun). From here I can see pretty much how to go forward, setting up variables, working in the loop etc. Setting pinMode I/O. I pushed the code to to the NodeMCU, open the Serial Monitor and all looks good. Below is my slightly modified version of the boiler plate code.
#include <adafruit_sensor.h> #include #include <dht_u.h> #define DHTPIN D2 #define DHTTYPE DHT11 // DHT 11 DHT_Unified dht(DHTPIN, DHTTYPE); float humidity = 0; float previousHumidity = -1; float temp = 0; float previousTemp = -1; unsigned long lastMillis = 0; void setup() { Serial.begin(9600); dht.begin(); sensor_t sensor; } void loop() { if (millis() - lastMillis > 1000) { sensors_event_t event; dht.temperature().getEvent(&event); if (isnan(event.temperature)) { Serial.println("Error reading temperature!"); } else { temp = event.temperature; if(previousTemp == -1 || temp != previousTemp) { Serial.print("Temperature: "); Serial.print(event.temperature); Serial.println(" *C"); previousTemp = temp; } } // Get humidity event and print its value. dht.humidity().getEvent(&event); if (isnan(event.relative_humidity)) { Serial.println("Error reading humidity!"); } else { humidity = event.relative_humidity; if(previousHumidity == -1 || humidity != previousHumidity) { Serial.print("Humidity: "); Serial.print(event.relative_humidity); Serial.println("%"); previousHumidity = humidity; } } lastMillis = millis(); } }
Reading data from the PIR
Now I have the DHT11 working I move to reading PIR data, which was nice and easy, there is no need for any extra includes, presumably because all a PIR does is send an interrupt which makes for a very simple piece of code, I just set up the pinMode and check the state from within the loop.
#define PIR D7 int pirState = 0; int pirPreviousState = 0; unsigned long lastMillis = 0; void setup() { Serial.begin(9600); pinMode(PIR, INPUT); } void loop() { if (millis() - lastMillis > 1000) { pirState = digitalRead(PIR); Serial.print("Motion output: "); Serial.println(pirState); } lastMillis = millis(); }
Putting it together
With very few changes to the original DHT11 sketch, I was able to incorporate my PIR code have the whole thing ticking along nicely, or so I thoguht.
#include <adafruit_sensor.h> #include #include <dht_u.h> #define DHTPIN D2 #define DHTTYPE DHT11 // DHT 11 DHT_Unified dht(DHTPIN, DHTTYPE); float humidity = 0; float previousHumidity = -1; float temp = 0; float previousTemp = -1; #define PIR D7 int pirState = 0; int pirPreviousState = 0; unsigned long lastMillis = 0; void setup() { dht.begin(); sensor_t sensor; pinMode(LEDPIN, OUTPUT); pinMode(PIR, INPUT); } void loop() { // Read PIR Every 250ms if (millis() - lastMillis > 250) { pirState = digitalRead(PIR); Serial.print("Motion output: "); Serial.println(pirState); } // Read DHT11 every 10secs if (millis() - lastMillis > 10000) { sensors_event_t event; dht.temperature().getEvent(&event); if (isnan(event.temperature)) { Serial.println("Error reading temperature!"); } else { temp = event.temperature; if(previousTemp == -1 || temp != previousTemp) { Serial.print("Temperature: ");Serial.print(event.temperature);Serial.println(" *C"); previousTemp = temp; } } // Get humidity event and print its value. dht.humidity().getEvent(&event); if (isnan(event.relative_humidity)) { Serial.println("Error reading humidity!"); } else { humidity = event.relative_humidity; if(previousHumidity == -1 || humidity != previousHumidity) { Serial.print("Humidity: ");Serial.print(event.relative_humidity);Serial.println("%"); previousHumidity = humidity; } } lastMillis = millis(); } }
the output initially looked fine but the PIR felt a little twitchy, sometimes it seemed to work and other times it didn't. I played around a lot with this but I wasn't confident in the results I was getting. In fact it was so sensitive I thought it was pretty unusable.
Fixing the false positives on the PIR
I got pretty frustrated at this point but while frantically reading blog posts I found that the HC-5201 has a voltage regulator on the board. The HC-5201 board is 3.3V and can take 5V in stepping it down for the PIR circuit and apparently you could bypass the regulator as the NodeMCU is 3.3V and the theory was that the regulator was interfering somehow. The trick was to place the +3V on to the spare jumper on the PIR when in short mode. I had varying results with this but it was more favourable than soldering a wire on to the back of the voltage regulator to bypass it. I was quickly out of my comfort zone as I know very little about electronics, enough to be dangerous, so I wrote this off as an ineffective 'fix'
Luckily I have came across a project that used the DHT11 and an PIR and which used a pull-up resistor on the DHT11 (I wasn't sure why). I decided to add the resistor and everything started to work as expeted. It seems the 4.7Kohm pull-up resistor was the missing link and if you look at the DHT11s that you can buy on PCB (with connector), you will notice a small thin film resistor on the board between the 3V3 and DATA OUT.
There is also a capacitor which could solve another problem where the sensor errors out intermittently. My theory here is that when the two devices are polled at the same time this impacts the voltage somehow and the DHT11 drops out. Tweaking the timings of the read on the DHT11 addresses this somewhat.
The end result was similar to the image on the right and with the device working I could move from the hardware to the Internetting of the Thing.
Connecting to the cloud (Cayenne IOT)
There had been a bit of chatter in the office about MyDevices (I will refer to this as Cayenne for the rest of this post), for connecting IOT to the cloud so I didn't really search around for other options and went in with no real expectations. Going from what I understood it seemed that you set up your device and most things automagically configure. I think that is possibly the case with the Pi but not so much with NodeMCU, that said I found the process pretty straightforward. And even uncovered how to auto register my device and sensors without the dashboard.
Cayenne allows you to create a device with the generic type of ESP 8266, there are also some useful links to the docs and github for all the things you will need for the Arduino IDE. I initially just went through the 'Hello World', which connects the Board to the service and walks you through the creation of a push button to light up the LED.
All the sketches are installed within the IDE and this is more than enough to get started. I chose the MQTT boiler plate as my start point. And quickly managed to light the onboard LED via the dashboard and my phone using the myDevices app (minutes of fun).
Pushing input to the device
All that is needed here is to add your device and install the libraries, take note of the 'username', 'password' and 'client id'
You then get directed you your dashboard, where you can add a device/widget, go to Custom and add a button.
You will end up with a button in your dashboard something like the following, (click on the settings icon/cog).
The really important parts are the Channel and I/O, the channel is what you use to I/O with the board. Or in this case just Input. Next we need to get the board connected. This is where you insert your credentials in to the new sketch and define the LED pin. On my version of the board this was D4.
The boiler plate already contains the code to connect, and we are not doing anything in the loop other than enabling Cayenne to access it via Cayenne.loop. The I/O is done in the last section of the code. You can see the parameters to expect as 'request.channel', 'getValue.getId()' and 'getValue.asString()';
All that needs to be done is check for the correct channel, in our case this is channel '0', then we simply use digitalWrite to flip the state of the LED. If all goes well this should now be turning on and off the onboard LED. For me this was enough of an orientation to start pushing data to cayenne after looking at a few more examples.
#define CAYENNE_PRINT Serial #include #define LEDPIN D4 // WiFi network info. char ssid[] = "ssid"; char wifiPassword[] = "wifiPassword"; // Cayenne authentication info. This should be obtained from the Cayenne Dashboard. char username[] = "username"; char password[] = "password"; char clientID[] = "clientID"; void setup() { Serial.begin(9600); Cayenne.begin(username, password, clientID, ssid, wifiPassword); pinMode(LEDPIN, OUTPUT); } void loop() { Cayenne.loop(); } //Default function for processing actuator commands from the Cayenne Dashboard. //You can also use functions for specific channels, e.g CAYENNE_IN(1) for channel 1 commands. CAYENNE_IN_DEFAULT() { CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString()); //Process message here. If there is an error set an error message using getValue.setError(), e.g getValue.setError("Error message"); if(request.channel == 0) { CAYENNE_LOG("Toggle power to LED", getValue.asInt(), getValue.asString()); digitalWrite(LEDPIN, !getValue.asInt()); } }
Pushing output to the Cayenne
Pushing raw data to cayenne once you are connected is easy, you can just use the 'virtualWrite' method in the loop. Cayenne.virtualWrite takes a channel as the first parameter in it's most basic form, it will take a data value and the second. You can then configure in the dashboard
Cayenne.virtualWrite(channel, digitalRead(YOUR_SENSOR));
This is fine for starters but you will end up configuring your widgets in the dashboard and while easy to do it is much simpler to specify the type of widget you want and the data type being sent. There is a handy chart which details all the types in the forum but for completeness I have added the properties I required.
Data Type | Type Value* | Unit | Unit Value* | Widgets* |
---|---|---|---|---|
Motion | motion | Digital (I/O) | d | twostate |
Relative Humidity | rel_hum | Percent (%) | p | gauge, value, graph |
Temperature | temp | Celcius | c | gauge, value, graph |
We are interested in in the non human readable columns marked '*', the widgets column shows your options for how the data can be viewed and to write to each of these in my setup is as follows, writing to channels 1-3 respectively.
Cayenne.virtualWrite(1, temperature, "temp", "c"); Cayenne.virtualWrite(2, humidity, "rel_hum", "p"); Cayenne.virtualWrite(3, pirState, "motion", "d");
The beauty of this approach is it saves work in the dashboard and if you change the properties in your virtualWrites you can change the view inside Cayenne. You can also set up different views on separate channels for the same device which could be useful. Merging this together with my base detection code I have this.
#define CAYENNE_PRINT Serial #include // WiFi network info. char ssid[] = "ssid"; char wifiPassword[] = "wifiPassword"; // Cayenne authentication info. This should be obtained from the Cayenne Dashboard. char username[] = "username"; char password[] = "password"; char clientID[] = "clientID"; #include <adafruit_sensor.h> #include #include <dht_u.h> #define DHTPIN D2 #define DHTTYPE DHT11 // DHT 11 DHT_Unified dht(DHTPIN, DHTTYPE); float humidity = 0; float previousHumidity = -1; float temp = 0; float previousTemp = -1; #define PIR D7 int pirState = 0; int pirPreviousState = 0; unsigned long lastMillis = 0; void setup() { Serial.begin(9600); Cayenne.begin(username, password, clientID, ssid, wifiPassword); dht.begin(); sensor_t sensor; pinMode(LEDPIN, OUTPUT); pinMode(PIR, INPUT); } void loop() { Cayenne.loop(); // Read PIR Every 1000ms if (millis() - lastMillis > 1000) { pirState = digitalRead(PIR); Serial.print("Motion output: "); Serial.println(pirState); if(pirState != pirPreviousState) { Cayenne.virtualWrite(3, pirState, "motion", "d"); } pirPreviousState = pirState } // Read DHT11 every 10secs if (millis() - lastMillis > 10000) { sensors_event_t event; dht.temperature().getEvent(&event); if (isnan(event.temperature)) { Serial.println("Error reading temperature!"); } else { temp = event.temperature; if(previousTemp == -1 || temp != previousTemp) { Serial.print("Temperature: ");Serial.print(event.temperature);Serial.println(" *C"); Cayenne.virtualWrite(1, temp, "temp", "c"); previousTemp = temp; } } // Get humidity event and print its value. dht.humidity().getEvent(&event); if (isnan(event.relative_humidity)) { Serial.println("Error reading humidity!"); } else { humidity = event.relative_humidity; if(previousHumidity == -1 || humidity != previousHumidity) { Serial.print("Humidity: ");Serial.print(event.relative_humidity);Serial.println("%"); Cayenne.virtualWrite(2, humidity, "rel_hum", "p"); previousHumidity = humidity; } } lastMillis = millis(); } }
It might not be pretty but this now allowed me to track motion and temperature in Cayenne, I tested it at home for a week and got some very solid results.
Using the same code on different networks
One big problem I came across was that I was working on the project in a few different locations and I really didn't want to maintain different builds when I was throwing code all over the place. So I built a little network scanning loop by checking out how Cayenne.begin connected to the Wifi. What I found, unsurprisingly enough was a WiFi class in the Arduino IDE folders. With a little bit of experimentation I was able to use WiFi.scanNetworks.
This method will return a collection of networks nearby which can be walked through, and you can test each one against the networks you want to jump on to by name. Then you just drop in to Cayenne.begin and connect that way. I also arranged my networks in order of preference. This was really handy and saved me a lot of time when switching from home to office, to tethered.
void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.disconnect(); int n = WiFi.scanNetworks(); // Check for valid networks for (int i = 0; i < n; ++i) { if(WiFi.SSID(i) == "SSIDNAME1") { Cayenne.begin(username, password, clientID, "SSIDNAME1", "pass"); } else if(WiFi.SSID(i) == "SSIDNAME2") { Cayenne.begin(username, password, clientID, "SSIDNAME2", "pass"); } else if(WiFi.SSID(i) == "SSIDNAME3") { Cayenne.begin(username, password, clientID, "SSIDNAME3", "pass"); } else if(WiFi.SSID(i) == "SSIDNAME4") { Cayenne.begin(username, password, clientID, "SSIDNAME4", "pass"); } } // The rest of setup goes here }
With the code ready I had to think about putting the device insitu at my grandmothers house, which meant it needed to be in an enclosure. So I stared to cobbled something together using what was lying around the house. The Cereal packet was my favourite but it wasn't really going to stand up (literally) but we had a 3D printer in the office and the guys in my team said 'we' could make a case.
Designing the enclosure
Naturally that 'we' meant 'I' could design it and they would print it , Chris and Phil we really helpful but the challenge was on to use CAD (another thing I have never done before). I looked at a few CAD applications but hit on TinkerCAD which is web based, I was hoping I could find a design and tweak it. I didn't have much luck but once I started using it I found it quite addictive. So with some pretty rudimentary tools (a ruler, pen and paper), I measured up my components and mocked the whole thing up in TinkerCAD.
It took a while to get going but the tutorials helped a lot and I ended up with something that would help me play around with size and position of the box with the items in it. You can view the model ESP826 Enclosure here.
A fair amount of effort went in to this and on the first print I had some trouble with the small shelf I have made for the DHT11 and the small lip I made for the lid to insert nicely in to the base. This was because of the tolerances of the LulzBot 3D printer. In summary if I made any protrusion in the design smaller than .4mm the printer would not be able to include it in the construction.
I made a few tweaks to the measurements, which is actually easier if you reverse the construction and use the parameter and positioning tools, I made a quick youtube video to give an example of using parameters to build a base and lid with an insert. This should give you a feel for how it is approached.
Final files:
https://tinkercad.com/things/aRihn3YoLTe
https://tinkercad.com/things/65y468Q5onI
The end result was successfully printed in about 4 hours and everything fitted perfectly which is astounding considering I didn't use measuring callipers.
The end result
The device is now in it's correct location and tracking data, which my parents can view over the web. There are alert features which can notify if movement is detected but unfortunately they are not really sophisticated enough for me to alert without sending a storm of emails and texts, but the graph is good enough for now. Perhaps as a next step I will look in to a web app which will aggregate the data and give a better view of the data collected and send alert that has more meaning.
Cayenne is great, very easy to get started and the DHT11 + PIR + NodeMCU is just a taster for me, I hope the post is useful to some other noobs out there
Top Comments