NodeMCU and PIR HC-SR501 (motion detector)

We have LCDs and we should have a data that can be displayed on them, we need data sources. So in parallel with displaying, let’s work on some sensor nodes.
First will be a motion detector, we gonna use a module with easy to remember name: PIR HC-SR501 🙂.
There are a few variants of it. But the main difference is an ability to use jumpers to change mode. My version can’t do that 😦
As a brain for sensor we will use the NodeMCUv3 board and as a language the LUA.

Module added to NodeMCU boilerplate @ GitHub

Planning

As always with NodeMCU we gonna add a module to boilerplate. Such module consists of a class and a handler for it.
On sensor board, you can find two potentiometers. One is for sensitivity and second for a delay.
Sensitivity goes from 3 meters (maximum right) to 7 meters (maximum left). Delay from 3s (maximum left) to 5 minutes (maximum right). How delay works? If a move is detected it change signal to low and resumes work after adjusted time. I set my sensor like this:

I mentioned that I cannot change mode on my PIR,  it is set to repeatable trigger. This is good because in this mode sensor gonna reset after a delay time. In other mode sensor stops after movement detection

We need to convert this into 2 events: movement and no movement.

Wiring

Only three pins! It is getting easier 🙂

NodeMCU           PIR
VU  ------------- Vcc
D3  ------------- Out
GND ------------- GND

Class

First I thought that it is simple, 1 if motion is detected and 0 when silent in the area but no. This is strange when I registered a timer it always returned high. But all description says that it should work. Nice, another uncommon event.

After some research I found out that it was the combination of bad code and bad pin 🙂 In a prototype, I made an error and did not use self.pin but the global pin and it was overwritten 🙂

The variable movement will keep current state. It is an integer.
We are going to use a gpio.trig on both edges (when 1->0 and 0->1) to set movement to false or true.

local PIRHCSR501 = {}
PIRHCSR501.__index = PIRHCSR501
PIRHCSR501.states = {
    "pir.nomovement",
    "pir.movement"
}

setmetatable(PIRHCSR501, {
    __call = function(cls, ...)
        return cls.new(...)
    end,
})

function PIRHCSR501.new(pin)
    local self = setmetatable({}, PIRHCSR501)
    self.pin = pin
    gpio.mode(self.pin, gpio.INT)
    self.movement = gpio.read(self.pin)

    local function alarm(level)            
        print(level)
        print(gpio.read(self.pin))
        self.movement = gpio.read(self.pin)
         
    end

    gpio.trig(self.pin, "both", alarm)
     
    return self
end

return PIRHCSR501

Now we need some code that would use this class (main.lua):

print ("core ready")

pir = require "pir_hcs_sr501"
sensor = pir(2)

It works. When I move it prints movement and after about 3s it changes to nomovement but only if I’m frozen.

Message and handler

We have some debug output but we need the network messages. In module network_message we have a nice two functions, first is prepareMessage(), it creates a table and fills it with base values. Next, we have a sendMessage(socket, message) that broadcast message via UDP. But there is one catch, we do no have a socket in PIR class 🙂
In all previous cases, our module or handler reacted for given message but this time sensor is the source.
Back to main.lua let’s create a broadcast UDP socket and pass it to the pir class:

print ("core ready")

network_message = require "network_message"
server_listener = require "server_listener"
pir = require "pir_hcs_sr501"

send_socket = net.createConnection(net.UDP, 0)
send_socket:connect(PORT, wifi.sta.getbroadcast())

print("ok")

sensor = pir(send_socket, 2)

And if we are passing a new parameter we need to adjust the constructor:

function PIRHCSR501.new(socket, pin)

Now we can use this socket and send the message. Change alarm function to:

local function alarm(level)             
        self.movement = gpio.read(self.pin)       
        message = network_message.prepareMessage()
        message.event = PIRHCSR501.states[self.movement + 1]
        network_message.sendMessage(self.socket, message)     
    end

And now we have nice messages in our network and any devices can react to it.

There is one functionality left, what if we want to ask for a current state? This is a job for a handler!
You can read more about NodeMCU and handler concept here: https://koscis.wordpress.com/2017/01/22/nodemcu-event-handlers-18b20-as-good-start/

Our handler has a simple task, react to event pir.move with a proper answer. We store current state in self.movement so we just need a getter:

function PIRHCSR501:get_state()

    return PIRHCSR501.states[self.movement + 1]
end

With this we can write our handler:

local pir_hcs_sr501_handler = {}
pir_hcs_sr501_handler.__index = pir_hcs_sr501_handler

setmetatable(pir_hcs_sr501_handler, {
    __call = function (cls, ...)
        return cls.new(...)
    end,
})

function pir_hcs_sr501_handler.new(node)
    local self = setmetatable({}, pir_hcs_sr501_handler)
    self.node = node
   
    return self
end   

function pir_hcs_sr501_handler:handle(socket, message)
    response = false
    if message ~= nil and message.event ~= nil then
        if message.event == 'pir.move' then
            message = network_message.prepareMessage()
            message.response = self.node:get_state()
            network_message.sendMessage(socket, message)
            response = true

        end
    end

    return response
end

return pir_hcs_sr501_handler

If the message with the event pir.move arrives, handler sends back the current state.

Now we can use all this stuff in main.lua:

print ("core ready")

network_message = require "network_message"
server_listener = require "server_listener"

pir = require "pir_hcs_sr501"
pir_handler = require "pir_hcs_sr501_handler"

send_socket = net.createConnection(net.UDP, 0)
send_socket:connect(PORT, wifi.sta.getbroadcast())

sensor = pir(send_socket, 2)
handler = pir_handler(sensor)

-- add handlers to listener
server_listener.add("pir", handler)

print("ok")

-- run server
server_listener.start(PORT)

Summary

We can detect a move! Nice:) We have two events but if you play a little bit with it, you can see that they are quite annoying. Why? If you move, it is detected, when you do not move for few sec or do a small move it reacts with no movement. It spams messages quite frequently. But it is just a sensor. Our control node should parse it correctly and take proper actions.

Advertisements

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s