Skip to content

09 Project 2: The $6 Remote Logging and Notification over IP Home Security System

thelionroars edited this page May 24, 2015 · 1 revision

In this project we will build a home security system for about $6 (We take no responsibility if your things get stolen, this is a hobby/proof of concept project!), demonstrating one of the ESP8266's biggest advantages... its darn cheap!

##Outcomes

  • Using digital sensors with the ESP8266.
  • Using the PWM functions of NodeMCU.
  • Understanding and using the MQTT protocol for Internet of Things.

##Equipment
For this project I have used the following equipment.

  • ESP8266_012 evaluation board. You can use an esp8266-01 for this project as you only require 2 GPIO pins, however please read the note under wiring step 3
  • Small breadboard. You could do this without one, but I didn't have a soldering iron
  • PIR motion detector.
  • DuPont cables.

##Wiring
Refer to the table at the end for a summary

  1. Connect the ESP8266 to your PC via TxD and RxD.

  2. Create a ground row on the breadboard(refereed to herein as Bb_G) from the ESP8266.

  3. Connect an internal PC speakers (square wave speaker) ground to the breadboard ground row(Bb_G), and its positive (+) to an ESP8266 GPIO. For the code example to run without modification, we are using GPIO12.

  4. Connect the PIR motion sensor.
    PIR Pinout

    1. Ground to the breadboard ground channel(Bb_G).
    2. Out to an ESP8266 GPIO, For the code example to run without modification, we are using GPIO13.
    3. Positive volts to a new breadboard channel (refereed to herein as Bb_P).
      Power
  5. Connect from a 3.3v power source to Bb_P. In my setup I used the Vcc out from my USB to serial board.

  6. Provide the ESP8266 chip with 3.3v power from another source, such as batteries and a voltage regulator.

We are not using the same power source for the PIR and ESP8266 as the battery on the ESP8266_012 doesn't seem to supply adequate amperage for both during WiFi connection. You may experiment freely with power to find what works, but if you are having trouble connecting to your WiFi try looking here.

##Lua Code

  1. Open ESPlorer and open the com connection.
  2. Send the following code to the ESP8266. Take note to replace "YourSSID" and "YourPassword" with your networks SSID and WiFi password.
function w()
  status = wifi.sta.status()
  print("status: "..status)
  if status ~= 5 then
     tmr.alarm(0,500,0,w)
  else
     print(wifi.sta.getip())
  end
end

wifi.setmode(wifi.STATION)
wifi.sta.config("YourSSID", "YourPassword")
w()

The reason we loop via the alarm is to allow the chip some time to authenticate and connect. If you do not get output "status: 5" after 3 or 4 seconds, troubleshoot according to the output of the wifi.sta.status() call, as per the table below.

0: STATION_IDLE,  
1: STATION_CONNECTING,       --the delay may have been too short, try issuing a call to status again.
2: STATION_WRONG_PASSWORD,  
3: STATION_NO_AP_FOUND,  
4: STATION_CONNECT_FAIL,  
5: STATION_GOT_IP.  
  1. Send the following code to the esp8266. Don't worry about the meaning yet, we will explain each section below in the code explanation section.
    You should replace "client:d5c1-47b3-b65f"(ln.11) with something unique
tmr.stop(0)

gpio.write(7,gpio.LOW)
gpio.mode(7,gpio.INPUT)
gpio.write(6,gpio.LOW)
gpio.mode(6,gpio.OUTPUT)
pwm.setup(6, 500, 512)

published=false

m = mqtt.Client("client:d5c1-47b3-b65f", 120, "user", "password")
m:lwt("/lwt", "offline", 0, 0)
m:on("connect", function(con) print ("connected") end)
m:on("offline", function(con) print ("offline") end)

m:on("message", function(conn, topic, data)
  print(topic .. ":" )
  if data ~= nil then
    print(data)
  end
end)

function poll()
 a = gpio.read(7)
 print('gpio13: '..a)
 if a==1 then
  if not published then
   pwm.start(6)
   writeToServer('motion detected at: '..tmr.now())
   published=true
  end
 else
  pwm.stop(6)
  published=false
 end
end

function writeToServer(msg)
    --m:connect("iot.eclipse.org", 1883, 0, function(conn) print("connected") end)
    --m:subscribe("/cab309eb-d5c1-47b3-b65f-90056d70d71d",0, function(conn) print("subscribe success") end)
    m:publish("/cab309eb-d5c1-47b3-b65f-90056d70d71d",msg,0,0, function(conn) print("sent") end)
    --m:close();
end

function w()
    print("connected")
    m:subscribe("/cab309eb-d5c1-47b3-b65f-90056d70d71d",0, function(conn) print("subscribe success") end)
    tmr.alarm(0,500,1,poll)
end

m:connect("iot.eclipse.org", 1883, 0, function(conn) w() end)
  1. You should now see print statements polling in ESPlorer. A 0 shows the PIR motion sensor is not reading motion, Whilst a 1 shows there is motion. Try sticking your hand in front of the PIR motion sensor.
  2. Did you feel the nostalgia? You should have* noticed three things then,
    1. The ESPlorer output started showing 1's.
    2. The Speaker made sounds.
    3. You saw a GUID, and a message like "motion detected at ###". This message was sent to a server on the internet by the MQTT protocol, then read back of the server, then printed by the ESP8266! Don't believe me? I will prove it in the next step.

*you didn't notice anything? here are some possible solutions

  • You are using different GPIO pins than the example code. The code assumes GPIO13 and GPIO12 are used. If you are using different GPIO pins (for example the ESP8266_01 only has GPIO0 and GPIO2) you need to alter the code accordingly.
  • You're too fast! The PIR can take up to 1 minute to warm up.
  • You need to calibrate the PIR motion sensor. Refer to the image in step 4 of the "wiring" section. Make sure the jumper is set to auto-reset (high) and adjust the sensitivity pot to find a good level.

##MQTT
MQTT is a protocol designed for the sending and receiving of small amounts of data, specifically for the Internet of Things.

In order to use MQTT there are 3 basic steps.

  1. Set up a 'Broker' server. A Broker server listens for messages and then sends them out to all clients who are subscribed to its 'topic'. You can build and run your own, or take advantage of open servers like I have in this project. Off course outside the prototyping realm we would not want to use an open server for our security system! You can see the server I'm using in the code explanation section.
  2. Set up a Client. A Client connects to a Broker server, subscribes to a topic and then either (or both) publishes messages and receives messages.
  3. Set up a second Client. There really wouldn't be much point in using MQTT if you don't need the data to get somewhere else would there? Just subscribe to the same topic, and you have communication.

Lets listen to our ESP8266. You could easily do this on another ESP8266, but since I'm a little low on power... I'm going to show you how to do it on your PC.

  1. Browse to the GIT page of the program called 'MQTT-spy' MQTT-spy. MQTT-spy is a GUI application for using MQTT. If you want to know more about it, the git page is right there!
  2. After running MQTT-spy, click 'Connections'/'new connections'.
    1. Use the default connection name (or you could use your name...)
    2. In the 'Connectivity' tab, in the 'server URIs' field enter 'iot.eclipse.org'
    3. In the 'security' tab, enable user authentication should be off.
    4. In the 'subscriptions' tab, click 'add topic' and replace the topic with '/cab309eb-d5c1-47b3-b65f-90056d70d71d' press enter key after pasting that in (you weren't crazy enough to type that in right?)
    5. Make sure 'create tab' is clicked.
    6. Apply, and then open the connection.
    7. Select the iot.eclipse.org tab at the top. You should have a tab with our topic's name.
    8. Right click it and resubscribe to it.
  3. OK! now MQTT-spy is running, lets see if its paying attention... So, wave your handing in front of the PIR motion sensor.
  4. Enjoy reading your wonderful message on your computer screen, all the way from a few centimeters to your left (or right?) ...via Ottawa, Canada!*

*the iot.eclipse.org server is in Canada

##Code Explanation
In this section various parts of the code are explained.

  • The PIR sensor works by putting out a high voltage when it detects motion.
  • Pulse Width Modulation (PWM) is essentially, the ESP8266 is switching this pin from high to low at a frequency of 500 times per second (the second argument).
gpio.write(7,gpio.LOW)
-- with gpio13 as an input, we can read the PIR Motion sensors output.
gpio.mode(7,gpio.INPUT)   
gpio.write(6,gpio.LOW)
-- this line is a bug fix, and might not be required in newer firmware versions
gpio.mode(6,gpio.OUTPUT)
 -- GPIO12 (index 6) is set up as a Pulse Width Modulator.  
pwm.setup(6, 500, 512)   
  • An alternative method would have been to set it as gpio.INTERRUPT, then code it to call the function 'poll' on change, removing the need for alarm 0 and the status boolean flag.
gpio.mode(7,gpio.INTERUPT)
gpio.trig(7,"both",poll) 

Next we have the poll function, which is where the code is run from.

function poll()
 a = gpio.read(7)       --read the current state of the PIR Motion sensor
 print('gpio13: '..a)   --print (more for debugging that anything else)
 if a==1 then           --a 1 indicates motion
  if not published then --we want to avoid spamming the server
   --start the square wave on the gpio, thus starting the sound in the speaker
   pwm.start(6)         
   --call the function which sends a message over MQTT
   writeToServer('motion detected at: '..tmr.now())  
   published=true
  end
 else
  pwm.stop(6) --stop the square wave on gpio
  published=false
 end
end

The poll function is called every half second by this line.

--args{0}: ID,args{1}: Milli-seconds,args{2}: repeat,args{3}: function to call on alarm.
tmr.alarm(0,500,1,poll) 

Finally we have the code relating to the MQTT protocol

--as we are using an open server that does not authenticate, we can use anything unique for our id, anything for user and password. '120' is our keepalive. 
--If we do not receive anything for 120 seconds, the connection is closed (server goes down, WiFi connection lost ..etc.)
m = mqtt.Client("client:d5c1-47b3-b65f", 120, "user", "password") 

--this is the "last will testament" if the keepalive fails, this function runs (we will print "offline")    
m:lwt("/lwt", "offline", 0, 0) 

-- we assign a function to successful on "connect"                         
m:on("connect", function(con) print ("connected") end)

-- we assign a function to successful "offline" (we will print "offline")   
m:on("offline", function(con) print ("offline") end)
     
-- we assign a function to successful "message" (we will print the received message, if it contains data"). This is where we receive messages.
m:on("message", function(conn, topic, data)              
  print(topic .. ":" )
  if data ~= nil then
    print(data)
  end
end) 
function writeToServer(msg)
    --this is how we actually send a message to the broker.
    m:publish("/cab309eb-d5c1-47b3-b65f-90056d70d71d",msg,0,0, function(conn) print("sent") end)  
end

function w()
    print("connected")
    --define the subscribe function. Args are [topic identifier. You should use a GUID or other random,
    --QoS level, function to call when subscribed]. This is where we choose what messages to receive.
    m:subscribe("/cab309eb-d5c1-47b3-b65f-90056d70d71d",0, function(conn) print("subscribe success") end)
    tmr.alarm(0,500,1,poll) -- begin running the program
end

-- Define the connect function. args are [server, port, secure, function to call when connected]
-- once connection is complete, it will call w() which starts the program running.
m:connect("iot.eclipse.org", 1883, 0, function(conn) w() end)