NodeMCU based PIR motion sensor supporting WiFi and MQTT
19 Mar 2020 - tsp
Last update 20 Mar 2020
7 mins
This is a really really short tutorial on how to use the NodeMCU Amica and an
cheap PIR sensor board to build an WiFi capable motion sensor that publishes
motion status via MQTT. This tutorial will also use the Arduino framework
for the firmware because of itās really low entry barrier. This is really
an entry level project.
Basically all you need is (note: all links are Amazon affilate links, this
pages author profits from qualified purchases):
The software also uses the Adafruit MQTT library that can easily be installed
via the Arduino framework.
Assembly
The assembly is pretty simple. Connect the Vcc and GND pins of the PIR module
(on the linked HC-SR501 these are the outermost pins) to the 3V3 and GND pins
on your NodeMCU. Then select any GPIO pin like the D7 (used in this sample)
and connect the output pin of your module (middle pin). Thatās all thatās
required for your hardware setup.
Software
This is basically a simplified version of the firmware Iām previously
described for the Sonoff T4EU1C
Basically the structure of this code is pretty similar:
As usual for small Arduino programs Iāve used a bunch of definitions that
can be automatically filled in by your deployment system (like Jenkins,
etc.) that can also automate your OTA updates - or manually in case of a
small amount of nodes:
{FILLIN_WIFI_SSID} is the SSID of the network this node should connect
to
{FILLIN_WIFI_PSK} contains the preshared key. This sample does not
implement EAP-TLS, but thatās entirely possible using the ESP8266 SDKs
routines for enterprise WPA. In fact Iām using EAP-TLS for my personal
nodes (the artifacts included for WPA enterprise are actualy in there
to remove configuration for WPA enterprise to be able to test the code).
{FILLIN_MQTT_HOST} Hostname or IP adress of the MQTT broker. Iām using
RabbitMQ with the MQTT adapter enabled.
{FILLIN_MQTT_PORT} MQTT port that should be used. Depends on the usage
of SSL on the MQTT broker. Note that this sample does not use SSL but usage
of SSL is highly encounraged especially when using WiFi for your nodes.
{FILLIN_MQTT_USER} contains the username used during authentication on
the MQTT broker
{FILLIN_MQTT_PASS} is the password for the given MQTT user
{FILLIN_MQTT_TOPIC} contains the topic under which the JSON message will
be published.
{FILLIN_OTA_HOSTNAME} contains the hostname that should be announced
using mdns by this node.
{FILLIN_OTA_SECRET} is the shared secret thatās used by the OTA software
during upload. It should be different for each node and only be known to
the deployment system and the MCU.
Note that all secrets can be extracted from the firmware when someone
gets access to the device. This includes access to WiFi PSK, MQTT users and
password and OTA secrets. Because of this one should:
- Not trust the WiFi and not allow any traffic from the IoT devices network
to the outside world (or unauthenticated access to any devices on the network)
- Use different OTA secrets so one cannot patch the firmware of all attached
devices. Just generate them randomly for each device.
- Create a single MQTT user on the broker for each and every sensor and
restrict access to the required login and write operations. Do not allow
the users global access to your MQTT broker, do not allow subscription to any
other topics and do not allow configuration access of your exchanges. Minimal
permissions - as usual.
The code also supports the following configuration options:
OTA_ENABLE can be defined to allow over the air updating of the firmware.
MQTT_RECONNECT_INTERVAL is an reconnection intervall for the MQTT client
if it looses connection to the MQTT broker. Itās given in seconds and defaults
to 5
MQTT_REPORT_INTERVAL configures at which rate the sensor should transmit
an status report even if the state has not changed. This can be used like
an heartbeat to detect failure of sensors (might be especially interesting
when used with battery powered devices). It defaults to 15 and is given
in seconds.
MQTT_PROCESS_PACKETS_TIMEOUT specifies after which timeout the process
packets callback should be called. Note that this parameter normally should
not be changed and defaults to 150 microseconds
PORT_MOTIONSENSOR selects the port at which the motion sensor is
attached. Defaults to D7
PORT_LED selects the port at which an optional status LED is
attached. Since the NodeMCU contains a builtin LED at digital pin 0 it
defaults to D0. This might be interesting to verify operational
status of a sensor in the field.
Basically the code just initializes the I/O ports, in debug mode also
the serial port and dumps some information. Then it configures WiFi and
tries to establish a connection (which will be reconnected automatically again
after network loss). Storage of WiFi configuration in flash has been disabled
since the credentials are already contained in the firmware (one might extend
that in own versions - itās necessary when using EAP to be capable of exchanging
certificates on expiry). In case OTA hasnāt been disabled OTA will be initialized.
Itās a really good idea to support OTA as long as the keys as unique and secure.
In the main loop the application tries to reconnect to MQTT after an WiFi
connection has been established whenever MQTT is not active. Then it handles
transfer of MQTT packets periodically and verifies pin changes on the
GPIO pin. In case of a pin change or a timeout for periodic reporting
the mqttSendStatus function is invoked which assembles the JSON
message and passes it to the MQTT client for transmission.
The JSON message contains two fields:
status indicates if the motion sensor has been triggered (true)
or not (false).
changed indicates if itās a periodic report (false) or if there
has been an event (motion sensor triggered or stopped triggering - true).
The code
The code is available as a GitHub GIST
Some ideas for improvement (possible exercises)
- Move to an interrupt triggered system. One can simply attach to a
CHANGE
interrupt using attachInterrupt with a static ICACHE_RAM_ATTR void (void)
function. This function will be called whenever the pin changes from low to high
or high to low. One can then either set a flag or perform some operations directly.
This might be interesting for faster sensors that might be missed with the polling
loop.
- One might add the ability to flash the indicator LED on an MQTT event
to which one can subscribe by creating an
Adafruit_MQTT_Subscribe object
thatās bound to the mqtt object. In the setup function one then
binds an callback (by using setCallback function on the subscribe object)
that sets the desired action. This might be an interesting feature to identify
devices when triggering the identification step from an app or webpage.
- One might also add a second input (like a push button) to allow to triger
an identify event in the node itself that is published via an MQTT
message again. Might be interesting when one owns a larger number of nodes
and wants to assign them to different tasks in an automation system. In this
case one might also simply use a sensor ID in the MQTT topic to distinguish
between devices.
This article is tagged: Electronics, DIY, Home automation, ESP8266, Programming