Node Red
Table of Contents
Nodes in use
node-red
node-red-contrib-arp
node-red-contrib-hold
node-red-contrib-wemo-emulator
node-red-dashboard
node-red-node-pi-gpio
node-red-node-email
node-red-contrib-image-output
node-red-contrib-tfjs-coco-ssd
Node Red PIRcam Flow
Initialisation
On boot up the Inject node automatically runs to set a default value for the dashboard Numeric (Refresh Interval) and activate a Trigger node for the ARP node. A msg.payload of off is delivered to dashboard Buttons (PIR alerts and RECORD) to set their status (a delay in delivery was found to be beneficial).
Last Seen
The dashboard Text node (Face) uses a face icon to display the presence of household mobiles and a text timestamp of the last motion activity detected. The RPI-GPIO node (PIN: 7) has a debounce of 25ms, if its msg.payload going to the Switch node has a value of != 0 then it triggers the Change node to set the msg.payload $now i.e. current date and time, and msg.colour to red. After 2 seconds the Trigger node reverts msg.colour to black.
The datestamp in the payload is formatted using the AngularJS date format string in the Value format of the Text node to display day of Week, date in month and time.
<font color = {{colour}}>{{payload| date: 'EEEE,'}}<br>{{payload| date: 'MMM d,'}} <br>{{payload| date: 'h:mm a'}}</font>
The font color as set by msg.colour is distinct from the msg.color used for the face icon.
The face icon is displayed by the Text node's Label
<br><br><font color= {{color}} ><span class="material-icons">face</span></font>
the color of which is set by msg.color in the Function node (Away/Home) depending on whether any of the mobile phones are connected to the local network, as ascertained by the ARP node. The payload from the Function node is set to either on or off which determines what happens in PIR alerts, but needs to be deleted before passing the msg.color on to Text node (Face) icon so that datestamp in the Face node is not overwritten. A Filter node limits the flow to changes in the payload message.
Function node (Away/Home)
msg.color =(msg.payload!="")?"limegreen":"red";
msg.payload = (msg.payload!="")?"off":"on";
return msg;
The frequency that the Address Resolution Protocol is accessed is set at initialisation.
PIR alerts & Gate Controls
The Dashboard Switch (PIR alerts) enables PIR sensor alerts via Email, and provides visual indication of the status by changing the icon as well as its colour. Additionally, when set to "on" it activates the Record, piCam alert and piCam Switches. If set to "off" then piCam alert and piCam are switched off. PIR alerts Switch can be controlled by Alexa and is automatically switched to "on" whenever all the household mobile phones are off the premises. This Alarm system is automatically disabled when at least one mobile returns, however there is around a 2 min delay for its presence to be detected. If the Alexa Audio Alert is "on" then the Alarm system is disarmed immediately following the returning home announcement, by an "off" message sent to PIR alerts via the Link in node.
PIR alerts has the label {{"<font color=grey><b>PIR alerts</b></font>"}} and has icons notifications_active and notifications_off set to colour red and grey respectively. Pass through msg is also enabled so that the Wemo-emulator payload message of "on" or "off" can control "PIR alerts which has the same payload strings for on and off respectively, as required for pass through msg. Alexa has voice activated routines to control the Wemo-emulator. The ARP node has the MAC addresses of the mobile phones associated with the household so that whenever all the mobiles are are no longer on the local network the PIR alerts Switch automatically receives the msg.payloads of "on" or "off" from the Switch node (switch on/off) and if on then an automatic notification is sent via the Link out node to Mailer. Any change in the status of PIR Alerts results in updating of the Change node (set flow.PIRstatus) and the Function node (Gate: control).
Whenever the lastSeen DATE is updated (msg.payload SET $now()) the flow continues through Link in nodes to towards the Link out node (to Mailer). Whether or not an email motion sensor alert is sent is dependent on the Function node (Gate control) and the Dashboard Switch (PIR alerts). Messages arriving at the Function node from lastSeen DATE do not have a message topic, whereas those from PIR alerts have msg.topic set to "gate". If "Gate control" gets a messages with the msg.topic "gate" and a msg.payload of "on" from dashboard Switch node (PIR Alerts) then the gate is set to open to allow lastSeen Date messages to pass through to the Link out node to Mailer, otherwise its message is dropped.
if (msg.topic === "gate") {
context.pass = (msg.payload === "on") ? "on": "off!;
return null; // exit out early as it's just the control
}
if (context.pass) {
return msg; // if enabled pass msg
}
return null; // or drop it
The lastSeen DATE Change node's msg.payload of $now() is changed in the PIR Alert Change node with a Jsonata xpression so that the date and time are appended '☎ PIR alert HALL.... ' & $moment(payload).format("MMM-DD HH:mm") before being forwarded via the Link out node to Emailer.
An "on" msg.payload from the Switch node (switch on/off) originating from ARP passes to the Switch node (Gate: PIR off) and if flow.PIRstatus is set to "off" then msg.payload is allowed pass through and in so doing prompts a notification email of "HA PIR on". The msg.payload is also forwarded via the Link out node to Alexa Audio Alerts.
Record and Post to Sheets
This section provides the user with two Dashboard controls.
The RECORD switch which enables
recording of PIR movement activity in the cloud to Google Sheets
displaying of a Table of Recent Activity
revealing the Refresh Interval control
The RECORD switch is also set to "on" status whenever the PIR alerts Switch is changed to "on" status.
2. The Refresh Interval (minutes) Numeric control which sets an interval of 1 to 10 minutes between the registering of PIR movement activity in order to reduce multiple notifications.
Dashboard Record, Table Chart of Recent Activity and Refresh Interval control.
Record, Display and Post to Sheets
iFrame embedded Google Timeline charts stopped working!
May 2023
Any change in the on/off status of the dashboard Switch (RECORD) determines how the Switch node directs the Template nodes to either hide or show the dashboard Table.
"on" : {"group":{"show":["HA_PIR_Table"]}
or "off": {"group":{"hide":["HA_PIR_Table"]}}
Switching the RECORD status also updates the Change node (set flow.Rstatus), and the condition of the "flow.Rstatus" variable is used in the Switch node (Gate: Record if ON (i.e. Rstatus 'on')) to determine whether the msg.payload containing date and time from lastSeen Date via the Link in node should pass through.
The payload value from Dashboard Numeric node (Refresh Interval (minutes)) sets the "flow.refresh" variable in the Change node (set flow.refresh (minutes)) whenever it is changed. The variable is used in the Function node (Refresh Rate Limiter) to limit the number recorded PIR movement instances coming through the Gate: Record if ON.
// Discard Multi Messages - set by Refresh Interval
var interval = (flow.get('refresh')) // minimum interval between messages (ms)
context.lastTime = context.lastTime || 0;
var now = Date.now();
if (now-context.lastTime > interval) {
context.lastTime = now;
return msg;
} else {
return null;
}
The Trigger node (momentary request) sends a message payload of "1" followed by an empty payload two seconds later to the GET Http request node so that a Movement of 1 and nothing are recorded in Sheets in close succession.
A Status node (status PIR alerts) is used turn on the Record Switch whenever the Switch (PIR alerts) is changed to "on" status.
ALEXA Audio Alerts
The Inject node (Default Alert) sets an initial editable default message in the dashboard Audio Alert on startup. Whenever there is a PIR alert it is forwarded on to the ALEXA Audio Alerts group via the Link in node. Depending on whether the flow.LOCstatus is "away" or "home" the msg payload is set to flow.Audioalert (as set by the dashboard Audio Alert) or "Welcome Home" before being directed towards the MQTT node (Shout) on the raspberry pi paired with the Amazon Echo Dot bluetooth speaker. If the latter, the msg.payload is then set to "off" and forwarded to the PIR alert switch thereby stopping any further alerts. Whether the msg.payload gets through the Gate Function node depends on the ui Switch which can set the gate to be open or closed.
ui Switch
colour set by ui Template
<style>
/* Background colour of entire widget */
md-card.nr-dashboard-switch.Alexaudio {
background-color: #0094ce;
}
Function node
if (msg.topic === "gate") {
context.pass = (msg.payload === true) ? true : false;
return null; // exit out early as it's just the control
}
if (context.pass) {
return msg; // if enabled pass msg
}
return null; // or drop it
On entering or exiting the geofenced area, the principal mobile with the IFTTT sends an email containing the text "entered" or "exited" to the email account associated with the Node Red Flow. This is received by the IFTTT location Email in node, and as determined by the Switch results in a confirmatory location status email back to the principal mobile. The location status of "flow.LOCstatus" is set to "home" or "away" as appropriate in the Change nodes and msg.payloads set accordingly for an email notification via the Link out node to Emailer. If "home", then the payload is modified in the Change node set welcome msg and stored in the Hold node (hold Welcome msg). As payload Welcome Change node will have set the "flow.LOCstatus" to "home" the next PIR alert is directed to the Change node trigger welcome msg reset LOCstatus to away so that the stored welcome message is sent to Shout and Function node PIR to off sends a msg.payload of "off" to PIR alerts via the Link out node.
Audio Alert
As the pi 2B running HA PIRcam Node Red Flow does not have bluetooth, the Audio Alert Flow is on a pi that does. The Flow is on a raspberry pi Zero 2W paired with the Amazon Echo Dot bluetooth speaker and is accessed from the main HA PIRcam Flow via an MQTT node (Shout).
Google Translate is used to convert text to sound which is output as an mp3 which is converted by ffmpeg on the fly to a wav piped to aplay. When done on the fly the spaces in the url get request must be replaced by %20 in the msg.payload within the TTS function, and then enclosed in quotes.
TTS function node
msg.payload = encodeURIComponent(msg.payload.trim());// replace spaces with %20 for url GET REQUEST
msg.url ="https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=en&q="+msg.payload;
msg.payload = "'"+msg.url+"'";// need enclose REQUEST in quotes
return msg;
msg.payload = encodeURIComponent(msg.payload.trim());// replace spaces with %20 for url GET REQUEST
msg.url ="https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=en&q="+msg.payload;
msg.payload = "'"+msg.url+"'";// need enclose REQUEST in quotes
return msg;
piCam Alert
NB Webcontrol must have been enabled on MotionEYE as described in Camera section.
The piCam's Motion Detection mirrors the pirAlert status and so is automatically switched on when all mobiles are away from home. The Filter only allows pirAlert status.text to pass in the event of a change in the status "on" or "off" to trigger the piCam Switch automatically. The piCam Switch can also be set manually in the dashboard. A URL instruction is sent to the piCam via the HTTP GET Request to Start or Pause Motion Detection in MotionEYE and a UTF-8 string is returned indicating completion of the instruction, this is used in the Function node (Color Mapping) to set the msg.color of the Switch (piCam Alert) dashboard text. Green indicates that MotionEYE has responded to the instruction and Motion Detection is active.
//First need to remove all line feeds
msg.payload = msg.payload.replace(/[\r\n]/gm, ' ');
//then trim any spaces from end
msg.payload = msg.payload.trim();
switch (msg.payload) {
case "Camera 1 Detection paused Done":
msg.color = "grey";
break;
case "Camera 1 Detection resumed Done":
msg.color = "limegreen";
break;
default :
msg.color = "red";
break;
}
return msg;
The Switch (piCam Alert) sets flow.piCam to on or off, in the Change node (set flow.piCam). The Function Permit
if (flow.get("piCam")=="on"){
return msg;
}
else{
return null
}
determines whether a email notification is sent when movement is detected by MotionEYE and a notification is received in Node Red via the Http In Webhook (set to POST and url /webhook). So as not to be inundated with messages the Delay (Alert Rate Limiter) is set to 1 message per 30 seconds and all intermediate messages dropped. That a notification has been sent from MotionEYE is also indicated in the dashboard by forwarding the msg.payload from the Change node (PiCam Motion Detected) to the Color Mapping so that piCam Alert's text is set to red, the default colour. As the msg.payload is forwarded before processing in Permit then the indication is independent of setting the piCam Alert dashboard Switch to "on" for receiving email notifications. (Red piCam Alert text can also indicate that the camera is malfunctioning.)
The Inject Nodes RESTART, QUIT and END are for the webcontrol interface of MOTION.
A Call to a Web Hook can be set in MotionEYE either in the Motion Notifications or the File Storage settings.
The latter has the advantage that the msg.obj received by Node Red includes the location of the image and/or video file recorded when a motion is detected by the camera. This could be used to associate an image with a email (maybe try implementing this at a later date).
The Web Hook call in MotionEYE running on the Raspberry pi with the camera is directed to the Node Red PIR Raspberry pi's URL /webhook.
The Node Red HTTP in (Webhook) is set to Post and accept file uploads. In response to the MotionEYE Web Hook Call it triggers the Change node to set an email alert for the Link out node to Emailer. The flow is limited by the Alert Rate Limiter which discards multi messages. The Function node only allows any message to pass if the piCam alert switch is on, ie flow.piCam is on, otherwise it is discarded.
if (flow.get("piCam")=="on"){
return msg;
}
else{
return null
}
Accessing Motion Images & Videos
When alerted
For Camera 1 the playback addresses are :
For pictures
http://192.168.n.n:8765/picture/1/preview/plus_unique_date/and_time.mpg
For Static image of initial movement in Video
http://192.168.n.n:8765/movie/1/preview/plus_unique_date/and_time.mp4
For Video
http://192.168.n.n:8765/movie/1/playback/plus_unique_date/and_time.mp4
The File Storage call to a Web Hook can be used for finding the unique name of the image or video, so that it can be appended to the playback http address. This can be done using JSONATA in a Change node
e.g. For pictures the msg.payload from Http request, an Http In (set to POST and url /webhookfile) is set to the value in a Change node
$replace(req._parsedUrl.query,"/var/lib/motioneye/Camera1","http://192.168.n.n:8765/picture/1/preview")
and for the Static image of initial movement
$replace(req._parsedUrl.query,"/var/lib/motioneye/Camera1","http://192.168.n.n:8765/movie/1/preview")
The Change node (extract URL) sets msg.url as described above for e.g. the Static image of initial movement. An Http Request (set to: GET, and payload as Send as request body, with Return as a binary buffer) is processed by Base64 (Action : Encode as Base64) and output in a ui Template (set to: Reload last value on refresh - so that the image persists in the dashboard on refresh). Using the following Template
<style>
img{
object-fit:contain;
}
</style>
<img src="data:image/png;base64,{{msg.payload}}" />
the image fills the desired grid pattern set by Size.
While this technique is useful for accessing individual images and videos through the Web Control Interface as and when notified by the Webhook it does not lend itself to accessing prior recordings but can be useful for providing TensorFlow with the motion file image for recognition analysis.
SSH into your Pi runing MotionEYE WITHOUT a Password with SSH Keys
In rpi 1 terminal hosting Node Red type and enter
ssh-keygen
Accept default file in which to save key
then asked whether you want a pass phrase so ENTER and again for verification.
Copy over the RSA key to the other rpi 2 runing MotionEYE by entering into the terminal
ssh-copy-id pi@[ip address]
you will asked for the password for the rpi 2, so authenticate with password for rpi 2
Then it is possible to SSH into rpi2 from rpi 1 without having to enter a password.
SSH access will allow full access of files on rpi2 from rpi.
Image Activity
Input needed for hiding Table/Image
An Image is only displayed in the dashboard if there has been a piCam Alert. The alert is forwarded to the Image Activity section from the piCam Alert section through changes in Color Mapping msg.color. The filter node blocks the flow of msg.color unless value has changed. The Function (ImgShow?) sets a context variable to True or False flow.set("ImgShow",(msg.color==="red")?true:false); so that the switch node can make different templates for the ui control depending on whether there was a piCam alert.
For TRUE
{"group":{"hide":["HA_PIR_Table"],"show":["HA_PIR_Bits"],"focus":true}}
For FALSE
{"group":{"show":["HA_PIR_Table"],"hide":["HA_PIR_Bits"]}}
The reason for using a context variable flow.ImgShow is that whenever a device opens the dashboard it must determine the template status to be applied to the final ui control. The Connect ui control (outputs on Connect, lost, change tab or group events) acts as a trigger and ensures that the correct template is applied.
The ui Buttons View Image and View Table appear in Table and Image groups respectively and act as toggle buttons to display the groups through the Function node Table or Image.
flow.set("ImgShow", (flow.get("ImgShow")==false) ? true : false);
msg.payload = flow.get("ImgShow")
return msg;
ssh Find Motion mp4
While the Webhook in the piCam Alert section alerts when movement has been detected in MotionEYE, the Webhook Img notifes whenever a Motion File has been saved by MotionEYE, and causes the Exec node to run a bash script
ssh pi@192.168.n.n 'find /var/lib/motioneye/Camera1/*/ -type f -name "*.mp4"' | sort -r
which finds all the MotionEYE mp4 files on the Pi running MotionEYE and orders them with the most recent first in a msg.payload string. The Function node (Motion Files Array Collation) fragments the msg.payload string into an array consisting of just the date and time with the mp4 extension.
const myarray = msg.payload.split('/var/lib/motioneye/Camera1/');
msg.payload = myarray.slice(1);
return msg;
The array is the Split and the individual elements are prepended with the Webview information for the Static image of initial movement in Video
msg.payload = "http://192.168.n.n:8765/movie/1/preview/"+msg.payload;
return msg;
before rejoining the elements back into an array and saving the msg.payload array as a flow.MotionFiles.
Swipe through Motion Files
The Webhook Img also triggers with a suitable delay the ui Button Refresh Motion Files array whenever a new Motion event is saved to flow.MotionFiles, and along with a "change event" in the ui Control resets the index for the array in the Change node set msg.i.
Having reset the the MotionFile Index with msg.i the Change node (url MotionFile;Set Count) adds to the message output the flow.MotionFile as the msg.payload and determines the size of the array and added as msg.Count, and sets the msg.url to the Index value i of the msg.payload.
msg.payload = flow.MotionFiles
flow.Count - $count(payload[])
msg.url = $trim($.payload[$$.i])
The Change node (Add to msg: Flow Vid and Datestamp) manipulates MotionFiles' element in the msg.url to create a playback video url msg.vid. The datestamp is also extracted from the url and formatted. msg.Count and msg.Index are also created for use in the ui Text.
msg.vid = $replace(url,"preview","playback")
msg.datestamp = $replace($substringBefore($substringAfter(vid,"playback/"),".mp4"),"/","T")
msg.datestamp = $substringBefore(datestamp,"T") & "T" & $replace($substringAfter(datestamp,"T"),"-",":")
msg.datestamp = $moment(datestamp).format("MMM-DD HH:mm")
msg.Count = $flowContext("Count")
msg.Index = msg.i+1
url MotionFile; Set Count
Add to msg: Flow Vid and Datestamp
MotionFile Index
ui Template
<style>
img {
object-fit: contain;
}
.video{
object-fit: contain;
border:3px solid red;
}
</style>
<script>
// include controls for video on iPhone
document.getElementById("myVideo").controls = (navigator.platform == "iPhone") ? true : false;
</script>
<img ng-src = "{{msg.url}}"
ng-swipe-right = "send({i:msg.i+1})"
ng-swipe-left = "send({i:msg.i-1})"
ng-hide = "showToggle"
ng-click = "send({i:msg.i});showToggle=!showToggle"
/>
<span class="video" ng-dblclick = "showToggle=!showToggle;send({i:msg.i})">
<video id ="myVideo" ng-src="{{msg.vid}}" autoplay width="100%" height="100%" playsinline poster >
</video>
</span>
The ui Template displays the Static Image (msg.url) or Video (msg.vid) which can be swiped through using Angular directives. The Change node (MotionFile Index) ensures that the upper and lower limit of the array is not exceeded when swiping.
msg.i = i>=$flowContext("Count")?$flowContext("Count")-1:i
msg.i = i<=0?0:i
Alert Limit
Since the alert is dependent on the MQTT connection then a warning in the event of disconnection needs to be displayed on dashboard. In the previous § Image Activity, the camera icon in View Image displays red background when MQTT server is disconnected, yellow when reconnecting and white when connected.
Any motion detected by MotionEYE results in an alert notification arriving from § piCam Alert node group which is then held in the hold node. The alert is only released from there if its associated image file contains a person as determined by Tensorflow. Any alert that hasn't been released gets overwritten. In this way false alerts due e.g. to changes in lighting are avoided, though the 'false' motion files are still retained.
However, if the MQTT server links are broken then alerts are not limited to those associated with an image file containing a person instead all alerts pass through to the § Mailer., as determined by the MQTT status and Function (Gate) nodes.
The URL of the Webhook Img sent from the § Image activity group of nodes is determined and assigned to msg.url in the Switch Node (Extract URL) by $replace(req._parsedUrl.query,"/var/lib/motioneye/Camera1","http://192.168.n.n:8765/movie/1/preview") for the http request node to GET as a binary buffer and to send as the image binary to the mqqt out node for processing by the TFSJ Restrict PiCam Alert to Person Class node group.
The MQTT Status and Gate Function nodes
The Status node monitors MQTT node (sensors/kitchen/camera) and in the Change node
so that the Boolean payload is only TRUE if the status.fill of the MQTT node is green, i.e. connected.
Gate All Function
function setStatus() {
if ((context.get('gate')===true)) {
node.status({fill:"red", shape:"dot", text:'closed'});
} else {
node.status({fill:"green", shape:"dot", text:'open'});
}
}
if (context.get('gate') !== true ) {
context.set('gate', false);
setStatus();
}
if (msg.topic == "gate") {
context.set('gate',msg.payload);
setStatus();
return;
}
if (context.get('gate') === false ) {
return msg;
}
return;
Gate Limit Function
function setStatus() {
if (!(context.get('gate')===true)) {
node.status({fill:"red", shape:"dot", text:'closed'});
} else {
node.status({fill:"green", shape:"dot", text:'open'});
}
}
if (context.get('gate') !== false ) {
context.set('gate', true);
setStatus();
}
if (msg.topic == "gate") {
context.set('gate',msg.payload);
setStatus();
return;
}
if (context.get('gate') === true ) {
return msg;
}
return;
Here the image is analysed by the tf coco ssd node. If the node returns a msg.class.person then the switch node passes it on to the set msg.payload node which sets msg.payload to true. The message is returned to the Alert Restrict node group via the mqtt nodes (sensor/kitchen/person). In the Switch node msg.trigger is set to the returned msg.payload value of True, which releases the alert from the hold node. The alert is sent to mailer.
Mailer
Alerts/Notifications are best sent to the mobile device using an email account used only for the Node Red Account. The Change node sets msg.topic to the desired email header while the message comes from the Link in node.