PIR With ESP8266 and MQTT

Introduction: PIR With ESP8266 and MQTT

About: an environmental researcher, learning electronics, programming, and sensors by tinkering

Well, this is yet-another motion sensor summary. A well-written tutorial from Adafruit is linked at the end of step 1. This tutorial is the part 2 on the topic of current limiting for LEDs, MQTT, and motion sensor. Part 1 is here.

PIR (Passive infrared sensor) or motion sensor is as its name implied detects a heat source that moves. Human, dog, cat moving across the front of the sensor triggers a HIGH signal. Here is a very nice diagram from Adafruit about the PIR sensor. PIR sensor is lost cost, and the response time is instant. The sensor can be configurated into several setups. Three setups covered in this writing included are:

  1. Sensor and LED share a microcontroller such as an ESP8266
  2. Separated sensor and other clients (light, database) communicates using MTTQ protocol
  3. Integrated with database and HTML to analyze the motion pattern with time stamp

Step 1: Basics About Motion Sensor (PIR)

This step covers the basics about part/form of PIR sensor, Arduino code to learn about the PIR, and a dedicated video to show what L setting looks like on the larger PIR version.

There are two (physical) forms of PIR sensors. One is smaller and looks simpler that costs about $1.28 on Aliexpress. The larger version (HC SR505) has two trimmer pots for the time delay and sensitivity setting, a jumper to select auto-trigger, and costs about $0.82 on Aliexpress. Both forms has an identical voltage regular (HT7133) and can accept the same input DC (max 24V, min ~5.0V). The typical current HT7133 output at 5.5V input is about 30mA, so soldering VCC wire to ESP8266 could overheat the IC. The ESP8266 consumes 100mA on average.

The link to Adafruit has a detailed calculation of the time delay for the larger PIR version. For the larger version, the lowest (shortest) time delay setting relays HIGH status in 4-5 seconds after the last motion is detected. A half-way setting relays about 117- 120 seconds in HIGH status.

I was confused about H-L jumper on the larger version. If you are, watch the YouTube video about the L setup - or not auto trigger. With a jumper on L, the HIGH on Pinout only if there was a LOW period in between HIGH period. The smaller version does not come with this setup and has the only AUTO-TRIGGERING mode.

Below is the code I used to upload to an Arduino Nano:

void setup() {

<br>  Serial.begin(115200);
  pinMode(4, INPUT);  //Pin 4 <> the larger PIR
  pinMode(5, INPUT);	//Pin 5 <> the smaller PIR
  pinMode(2, OUTPUT);	//Pin 2 <> blue LED (indicates the status of the larger PIR)
  pinMode(11, OUTPUT);  //Pin 11 <> red LED (indicates the status of the smaller PIR)<br>
int id = 0;
void loop() {
  int lPIR = digitalRead(4);
  int sPIR = digitalRead(5);
  digitalWrite(2, lPIR);
  digitalWrite(11, sPIR);
  Serial.print("\tLarge PIR:  ");
  Serial.print("\tSmall PIR:  ");

1. Adatafruit has a detailed writing about PIR sensor from the basic, wiring, connect with Arduino and with Python-enabled devices

2. This online listing has a detail specification of the larger PIR version.

Step 2: LED Light and Motion Sensor Shared One ESP8266

This setup can be implemented with Arduino MCU unless you want to have the wireless communication for an optional control such as by a Control Terminal or using the MQTT to collect data.

I have posted detailed steps on how to build the limiting current for LEDs. Here is the link again. I would not repeat the required parts here, the only difference is the main resistor, which is 1R 2W or 3W versions.

Another important component of the build is the software. If you using the larger PIR sensor, you have a rough estimation of the HIGH status of the Pinout (the middle pin in the PIR sensor) between 5 seconds, to a mid-way turn by the trim pot gives you about 2 minutes of HIGH signal out after the last movement was detected. The smaller PIR only relays the HIGH status about 5 seconds, so it can be inconvenient that you have to move continuously in front of the sensor to keep the LEDs on. If you just moved out of the detecting region or stood still for a few seconds, the DATA pin status is LOW, and the LEDs is OFF.

To add a defined delay by the software, I implemented a reset loop inside the main loop. During the last movement was detected (or digitalRead(pir) == 1), the counter is incremented to the maximum time range called onRetain. During that time, if a movement was detected, the counter is set to zero, and the loop starts over.

The snippet of the code is below:

unsigned int onRetain = 30;  //set a 30 seconds delay after the last movement is detected

if (lightState == 1){
	for (int i=0; i < onRetain; i++){
        pirRead = digitalRead(pirPin);
        if (pirRead == 1) {
          i= 0;

Here is the entire code to upload to the ESP8266 hosted on GitHub . I used 1M-flash version of ESP8266. I made a kitchen light using this approach.

Step 3: Motion Sensor Is Separated From the LED Light

This is where using MQTT to replay the motion status shines. The motion sensor published to one specific topic, and other clients, either LED light or a database, to listen in. This approach does not limit the number clients can be controlled by one motion sensor. The drawback is at least two MCUs needed, one for motion sensor, and the other for light.

The concept is similar to the previous tutorial (as in step 1) using a control terminal, but instead of manually pushing the message, the PIR client will automatically publish a message in JSON format to the desired topic such as sensor/door/pir.

Customize the topic in the provided code as you wish, only to make sure that both the publisher and subscribers share the same topic. Here is how I defined a topic on the publisher side. The entire code is posted on GitHub.

#define light_state_topic "sensor/door/pir"
#define SENSORNAME "pirOne"

And on the subscriber side, and the full code to upload to ESP8266 on GitHub:

#define mqtt_port 1883
#define subscribe_topic "sensor/door/pir"
#define SENSORNAME "One1W"

In the video, you can see that the HIGH status of PIR retained about 5-6 seconds after the last motion while the subscriber (LEDs) is ON for 12 seconds. To change the duration for the LED to be on, change the value of DELAYS variable in the code of the subscriber:

#define DELAYS  12 //delay 12 second after the PIR sensor OFF

The DELAYS variable is fed to a function to keep track of the HIGH status of the PIR publisher. Calling the client.loop() during the delay is what I came up with the update the status of PIR sensor while counting up to the max delays by DELAYS.

void setLeds(int onSeconds){
    float prev = 0;
    if (stateOn==1){
    brightness = 1000;
    analogWrite(controlPin, brightness);
    analogWrite(ledPin, brightness);
    Serial.print("\t>>  analogWrite:\t");
    for (int i=0; i 10)) {
        prev = millis()/1000;
      Serial.printf("\nstateON is %i \t", stateOn);
      Serial.printf("\t Counting on i: %d \n", i);
   } else {
    brightness = 0;
    analogWrite(controlPin, brightness);
    analogWrite(ledPin, brightness);

And finally, call this function up on the MAIN LOOP:


This would be it. In the next step, we will work with a database and a web server.

Step 4: With SQL Storage and HTML Displaying

It is 2018. Only turning the LEDs on automatically is not sexy enough. In this part, I will collect the motion status from my working table, and stored the data into an SQL database, and display the data on the browser using Flask and Plotly.

The concept is similar to Step 3, but instead of an LED light, this is the case I use a Python script to listen to the topic. One modification on the circuit was I use a pre-made 3.3V regulator such as this one , costs $0.88 on Aliexpress rather making 3.3 voltage regulator from AMS1117 IC.

First, I created a database and a table by SQLite3. Install sqlite3 if you not done so:

pip3 install sqlite3

and a table schema for the timestamp and the status of PIR. Starting in a terminal:

sqlite3 pirs.db
CREATE TABLE pirone (thetime DATETIME, pirstatus INT);

Then, we need to write a script to listen to the topic. I used Paho-MQTT Python . Starting by install the library:

pip install paho-mqtt
or pip3 install paho-mqtt --user

And here is the full Python script:

#! /usr/bin/python3<br>import time
import sqlite3, json
import paho.mqtt.client as mqtt
mqtt_topic = "sensor/door/pir"
mqtt_username = "janedoe"
mqtt_password = "johndoe"
dbFile = "pir.db"  # this assumes that the database file and the script are in the same folder
mqtt_broker_ip = ''
def takeTime():
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    client.subscribe(mqtt_topic, 0)
# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    time_ = takeTime()
    topic = msg.topic
    payload = json.dumps(msg.payload.decode('utf-8'))
    sql_cmd = sql_cmd = "INSERT INTO {1[sensor]} VALUES ('{0}', {1[motion]})".format(time_, payload)
    return None
def writeToDB(sql_command):
    conn = sqlite3.connect(dbFile)
    cur = conn.cursor()
client = mqtt.Client()
client.username_pw_set(username=mqtt_username, password=mqtt_password)                                                                                                                   
client.connect(mqtt_broker_ip, 1883, 60)
client.on_connect = on_connect
client.on_message = on_message

Third, to get the data back from the SQL database, you need a sql command like this one.

sql_command = """ SELECT * from pirone ORDER BY thetime DESC LIMIT 2000;"""

A literal meaning is 'select everything from table pirone, then sort the data by thetime column in descending order, and limits the number of rows by 2000'.

The final package of the Flask web server and database and Python script are posted on GitHub as well.

The recommended to run a Flask web server to use virtualenv; running on the system libraries are fine as well.

First, install the required libraries for the web server:

pip3 install flask
pip3 install plotly

To run the web server, change the port as desired, and,

python3 app_pi.py 

Access the web server in the same computer by URL:


or from other computers through the IP address of the one running the webserver such as:

To stop the web server, press Control + C.

That is about it. I hope you this tutorial is helpful.



    • Tiny Home Contest

      Tiny Home Contest
    • Water Contest

      Water Contest
    • Fix It! Contest

      Fix It! Contest