Introduction: Light & Motion Sensor With NodeMCU

About: Undergrad electronics engineering student majoring in Communications

The title generally describes the system, but there are more deliverables that we had to showcase for our Capstone project. This instructable will explain how to connect a light sensor and a motion sensor to NodeMCU ESP8266 and push the data to the IoT platform ThingSpeak, with an additional feature of a simple Lux meter. Here are what my sensor network system is supposed to do and how it functions:

  • Light sensor (LDR) triggers LED 1
  • PIR motion sensor triggers LED 2
  • Corresponding light intensity value in lx is calculated
  • NodeMCU pushes data to ThingSpeak
  • ThingSpeak displays LDR, motion sensor & Lux value

Supplies

For the main system, you will need:

  • Breadboards (2)
  • Jumper wires
  • 22 ohm resistors (2)
  • 10k ohm resistor
  • USB Micro B cable
  • ESP8266 NodeMCU Lua V3
  • Light-dependent resistor (LDR/photoresistor)
  • HC SR-501 Passive Infrared (PIR) Motion Sensor
  • LEDs (2)

The 22 ohm resistors are just to limit the brightness of the LED. You may choose a higher value or not use them entirely. For the enhancement feature, a method you can do it is by measuring the Voltage & Resistance of the circuit. So, you will need:

  • Multimeter
  • Lux meter/lux meter app

Digitally, these software and tools will be used. So, prepare or install:

  • Arduino IDE
  • Octave or MATLAB
  • a ThingSpeak account

Step 1: Step 1: Setting Up Circuit

After connecting your ESP8266 to Arduino IDE (steps better explained here thanks to another user), set up the circuit as such:

  • The LDR is connected to the A0 pin of the microcontroller through a 10k Ω resistor that is grounded
  • The OUT terminal of the PIR sensor is connected to pin D8 of the microcontroller
  • The VCC terminal of the PIR sensor is connected to the VIN pin of the microcontroller
  • The GND terminal of the PIR sensor is connected to the GND pin of the microcontroller
  • LED 1 is connected to the D6 pin of the microcontroller through a 22 Ω resistor
  • LED 2 is connected to the D5 pin of the microcontroller through a 22 Ω resistor

Ground the two breadboards together (using the GND pin of the NodeMCU) and supply power by connecting the NodeMCU module to your laptop/PC via cable or a power adapter. The on-board regulator of the NodeMCU will supply a power of 3.3V.

Step 2: Step 2: Displaying Sensor Values

Now that the circuit is set up, let us upload a code that allows us to display the sensor values on Arduino's serial monitor. (It's a window that will pop up if you click the magnifying glass icon in the top right corner of the IDE). Connect the NodeMCU to your laptop or computer and launch Arduino IDE. Ensure that the Board, Upload Speed, and Port in the Tools menu are correct. This is the system's incomplete code, this is just to display the values on the serial monitor:

int ldr = A0;
int pir = D8;

void setup() {
 Serial.begin(115200);
 pinMode(ldr, INPUT);
 pinMode(pir, INPUT);
 pinMode(led_pir, OUTPUT);
 pinMode(led_ldr, OUTPUT);
}

void loop() {
 int ldr = analogRead(A0);
 Serial.print("Brightness Sensor: ");
 Serial.println(ldr);
 Serial.println();
 delay(150);
 
 int pir = digitalRead(D8);
 Serial.print("Motion Sensor: ");
 Serial.println(pir);
 Serial.println();
 delay(150);
}

Step 3: Step 3: Making a Lux Calculator

Lux is a standard measurement for light intensity, in datasheets it is usually in the S.I unit "lx". Take note that there are three values you need to jot down here.

  1. Take 10 random readings of the LDR by adjusting the brightness of the environment from bright to dim. This can be done either by different timestamps throughout the day OR take a cardboard (or use your palm) and move it closer to the LDR sensor, emulating a dark enclosure.
  2. As you take these readings, measure each of the corresponding resistance across the LDR using the multimeter (something like this).
  3. Take the corresponding Lux values using the lux meter or a lux meter mobile app on your phone. Our objective here is to generate our own equation for lux measurement calculation, by approximating the actual lux values of the environment.

Put these values in a table or an excel sheet if it is more convenient for you. So you'll have ten readings of 1. The LDR value (0~1024), 2. Resistance across LDR (Ω), and 3. Lux value (lx). Through voltage divider rule, we observe that the voltage across LDR technically can be written as:

Vldr = Vin * (R1/(R1 + Rldr))
Vldr = 3.3 * (10k/(10k + Rldr))

Rearrange it to get the resistance formula of LDR. We need this later for the Arduino program:

Rldr = (33k/Vldr) - 10k

The Vldr here is unknown, and we can obtain it through this scaling formula:

Vldr = LDR value * (Vin/1024)
Vldr = n * (3.3/1024)

Launch Octave and plot a graph of Lux vs Resistance.

n = [1024, 994, 873, 750, 608, 428, 367, 361, 134, 45];
R = [0, 301.81, 1729.7, 3653.3, 6842.1, 13925, 17902, 18366, 66418, 217560];
lux = [169, 99, 81, 60, 51, 25, 5, 4, 1, 1];
figure(1)
plot(R, lux)
xlabel('Resistance, ohm');
ylabel('Lux Reading, lx'); 

Replace the n, R, and lux values with your own. You'll notice that its a graph that goes exponentially down, and its difficult to generate a line equation with this. So, using the same values, plot a graph of Log Lux vs Log Resistance instead, by adding these lines of code:

log_R = [2.48, 3.238, 3.563, 3.835, 4.144, 4.253, 4.264, 4.822, 5.337];

log_lux = [1.996, 1.908, 1.778, 1.708, 1.398, 0.699, 0.602, 0, 0];

figure(2)
plot(log_R, log_lux, '.b') 
p = polyfit(log_R, log_lux, 1); 
f = polyval(p, log_R); 
hold on; 
plot(log_R, f, '--r') 
xlabel('Log Resistance'); 
ylabel('Log Lux');

Replace log_R and log_lux values with your own. You can add Octave command to calculate the logarithmic values, or in my case, I just used my calculator manually. When you run it, now you get a best fit line. You can zoom in and hover your cursor on the points to see their miniscule values in the top left corner.

Obtain the y-intercept and slope of the line. Generate a straight line equation such as below, then rearrange it to obtain lux as a function of R.

y = mx + c
log_lux = m(log_R) + c
lux = R^(m) * 10^(c)
lux = R^(-0.78) * 10^(3.934)

Now you have your own Lux formula! This will be added to the Arduino program later.

Step 4: Step 4: Connecting to ThingSpeak

Create a ThingSpeak channel, call it whatever you want. Add three fields corresponding to the Brightness, Lux, and Motion values. Copy your channel ID from the 'Channel Settings' tab to your clipboard. Also, copy the Write API Key from the 'API Keys' tab to your clipboard.

Step 5: Step 5: Upload the Full Code

I'll go over the codes to explain what it does, or you can upload the whole thing from my GitHub repository I linked at the end of this step.

Initialize with ESP8266, WiFi connection, ThingSpeak, and math library. Define a maximum lux value, you can use the one in the table that you noted down from all those lux readings.

#include <ESP8266WiFi.h>;
#include <WiFiClient.h>;
#include <ThingSpeak.h>;
#include <math.h>;
#define max_lux 170;

Now connect to your WiFi name (ssid) and the password. Initialize the value you'll be using. Replace Naaram and morgan123 with your own wifi ssid and password:

const char* ssid = "Naaram";
const char* password = "morgan123"; 
int ldr = A0; 
int pir = D8; 
int led_ldr = D6; 
int led_pir = D5; 
int motionstate; int i; float lux;

Then, declare the channel ID and API key that you copied to your clipboard. You know the drill, replace 1261003 and the API key with your own:

WiFiClient client;
unsigned long myChannelNumber = 1261003; 
const char* myWriteAPIKey = "77X7EU9ST8H0X9Q8";

Let's initialize the baud rate and the pin modes to input or output:

void setup() {
 Serial.begin(115200);
 ThingSpeak.begin(client);
 pinMode(ldr, INPUT);
 pinMode(pir, INPUT);
 pinMode(led_pir, OUTPUT);
 pinMode(led_ldr, OUTPUT);
 delay(500);
}

Like in Step 2, we obtain the sensors values and display them on the Serial Monitor. Using if else functions, program the NodeMCU to trigger the corresponding LEDs when the sensor values meet certain conditions. In a special case where LDR value is 1024, the program will output maximum lux value defined earlier and push that value to ThingSpeak.

void loop() {

int ldr = analogRead(A0); 
Serial.print("Brightness Sensor: "); 
Serial.println(ldr); 
Serial.println(); 
delay(150); 

int pir = digitalRead(D8); 
Serial.print("Motion Sensor: "); 
Serial.println(pir); 
Serial.println(); 
delay(150);

if(pir == HIGH){
for(int i=0;i<90;i++){ 
motionstate = 1; 
digitalWrite(led_pir, HIGH); 
Serial.print("Motion State: "); 
Serial.println(motionstate); 
Serial.println(); 
delay(500); } }

else{
motionstate = 0;
digitalWrite(led_pir, LOW);
Serial.print("Motion State: ");
Serial.println(motionstate);
Serial.println();
delay(500);}

if (ldr < 600) {
digitalWrite(led_ldr, HIGH);
delay(500); }
else {
digitalWrite(led_ldr, LOW);
delay(500); }

if (ldr == 1024){
Serial.print("Maximum LDR, lux limit: ");
Serial.print(max_lux);
Serial.println();
ThingSpeak.setField(3, (int)max_lux);
delay(500); }
else {
Serial.print("Calculating illuminance... ");
Serial.println();
delay(500); }

Then, below are the routines for our lux calculator. Replace -0.78 and 3.934 with the slope and y-intercept of your graph from the Octave step. To push the values to ThingSpeak, we initialize with the field number, data type, and corresponding variable.

float volt_ldr = ldr * (3.3 / 1024);
Serial.print("Voltage form of LDR: ");
Serial.print(volt_ldr);
Serial.println();

float R_ldr = (33000 / volt_ldr) - 10000; 
Serial.print("Resistance form of LDR: ");
Serial.print(R_ldr);
Serial.println();
float lux = (pow(R_ldr, -0.78)) * (pow(10, 3.934));
Serial.print("Calculated Illuminance, Lux: ");
Serial.print(lux);
Serial.println();

ThingSpeak.setField(1, (int)ldr);
ThingSpeak.setField(2, (int)motionstate);
ThingSpeak.setField(3, (float)lux);
ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
delay(150); 
}

You can upload the full code from this github repository.