Event Timeline for an MQTT Camera

Use Case

You are sitting in front of screen having to keep an eye on what is happening somewhere. You want to be able to see the live video from your camera, get a snapshot whenever the motion detection is triggered and have a event timeline so you can scroll back a few events and see what you have missed while fetching another coffee.

Used Nodes

Node-RED Flow

[{"id":"e1798e8c.cca37","type":"mqtt in","z":"a4f46023.3fb8f","name":"Alarm Server","topic":"instar/local/status/alarm/triggered","qos":"1","datatype":"auto","broker":"fbc900bc.b83bb","x":110,"y":394,"wires":[["32534aee.fd2d16","79b868e5.7d29c8"]]},{"id":"574bbd32.580584","type":"ui_statetrail","z":"a4f46023.3fb8f","group":"95e63e10.d8989","order":2,"width":"12","height":"2","name":"Timeline","label":"Timeline","states":[{"state":true,"col":"#9900ff","t":"bool","label":"Alarm"},{"state":false,"col":"#555353","t":"bool","label":"Idle"}],"periodLimit":"15","periodLimitUnit":"60","timeformat":"HH:mm:ss","tickmarks":4,"persist":false,"legend":1,"combine":true,"blanklabel":"waiting...","x":460,"y":394,"wires":[["1a5b3011.4c815"]]},{"id":"32534aee.fd2d16","type":"change","z":"a4f46023.3fb8f","name":"true","rules":[{"t":"set","p":"payload","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":394,"wires":[["574bbd32.580584","dc7eb3b5.1f024"]]},{"id":"34503a28.e49b16","type":"change","z":"a4f46023.3fb8f","name":"false","rules":[{"t":"set","p":"payload","pt":"msg","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":434,"wires":[["574bbd32.580584"]]},{"id":"dc7eb3b5.1f024","type":"delay","z":"a4f46023.3fb8f","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":300,"y":434,"wires":[["34503a28.e49b16"]]},{"id":"742cb138.906c5","type":"http request","z":"a4f46023.3fb8f","name":"GET Snap","method":"GET","ret":"bin","paytoqs":false,"url":"http://192.168.2.116/tmpfs/auto.jpg?usr=admin&pwd=password","tls":"","persist":false,"proxy":"","authType":"","x":470,"y":280,"wires":[["ae98ceb7.80362"]]},{"id":"81236253.61467","type":"ui_template","z":"a4f46023.3fb8f","group":"ae53a8a2.0e42c8","name":"Display image","order":1,"width":"12","height":"7","format":"<img alt=\"Snapshot\" src=\"data:image/jpeg;base64,{{msg.payload}}\">","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":780,"y":280,"wires":[[]]},{"id":"ae98ceb7.80362","type":"base64","z":"a4f46023.3fb8f","name":"","action":"","property":"payload","x":620,"y":280,"wires":[["81236253.61467","a16ec64e.8db7e8"]]},{"id":"95b5e592.3c2a28","type":"ONVIF Snapshot","z":"a4f46023.3fb8f","name":"ONVIF 8015","url":"http://192.168.2.116:8080/","interval":"5","username":"admin","password":"password","active":true,"x":130,"y":173,"wires":[["7cefec35.c2ae14"]]},{"id":"7cefec35.c2ae14","type":"ui_template","z":"a4f46023.3fb8f","group":"b022415d.c9b4a","name":"Display image","order":1,"width":"12","height":"7","format":"<img alt=\"ONVIF\" src=\"{{msg.payload}}\" />\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":307,"y":173,"wires":[[]]},{"id":"a16ec64e.8db7e8","type":"alasql","z":"a4f46023.3fb8f","name":"AlarmSnapBlack","query":"INSERT INTO AlarmSnapBlack VALUES (CURRENT_TIMESTAMP,?);","x":790,"y":320,"wires":[[]]},{"id":"60132398.ed0c6c","type":"ui_template","z":"a4f46023.3fb8f","group":"95e63e10.d8989","name":"Display image","order":1,"width":"12","height":"7","format":"<img alt=\"Snapshot\" src=\"data:image/jpeg;base64,{{msg.payload[0].event}}\">","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":1099,"y":477,"wires":[[]]},{"id":"79b868e5.7d29c8","type":"trigger","z":"a4f46023.3fb8f","op1":"1","op2":"","op1type":"str","op2type":"nul","duration":"8","extend":false,"units":"s","reset":"","bytopic":"all","name":"","x":314,"y":280,"wires":[["742cb138.906c5"]]},{"id":"1a5b3011.4c815","type":"switch","z":"a4f46023.3fb8f","name":"Alarm","property":"payload.label","propertyType":"msg","rules":[{"t":"eq","v":"Alarm","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":434,"wires":[["e0dfdd51.2cbaa"],[]]},{"id":"39fb50b4.f6781","type":"moment","z":"a4f46023.3fb8f","name":"","topic":"","input":"payload","inputType":"msg","inTz":"ETC/GMT","adjAmount":"0","adjType":"minutes","adjDir":"subtract","format":"HH:mm:ss","locale":"en_US","output":"payload","outputType":"msg","outTz":"ETC/GMT","x":859,"y":431,"wires":[["e882f2cd.e5b07"]]},{"id":"e0dfdd51.2cbaa","type":"change","z":"a4f46023.3fb8f","name":"Timestamp","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.timestamp","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":650,"y":385,"wires":[["39fb50b4.f6781"]]},{"id":"5aac28d.d6475d8","type":"alasql","z":"a4f46023.3fb8f","name":"SELECT","query":"SELECT event FROM AlarmSnapBlack\nWHERE ts LIKE $0","x":939,"y":477,"wires":[["60132398.ed0c6c"]]},{"id":"e882f2cd.e5b07","type":"function","z":"a4f46023.3fb8f","name":"","func":"timequery = '%'+msg.payload+'%';\nmsg.payload = timequery;\nreturn msg;","outputs":1,"noerr":0,"x":809,"y":477,"wires":[["5aac28d.d6475d8"]]},{"id":"4f4f2aeb.c54b64","type":"inject","z":"a4f46023.3fb8f","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":"5","x":130,"y":74,"wires":[["a1e5987a.3902d8"]]},{"id":"a1e5987a.3902d8","type":"alasql","z":"a4f46023.3fb8f","name":"CREATE","query":"CREATE TABLE AlarmSnapBlack (ts TIMESTAMP VARCHAR(80), event VARCHAR(255));","x":283,"y":74,"wires":[[]]},{"id":"feff22f8.884e3","type":"comment","z":"a4f46023.3fb8f","name":"Create Database on Start","info":"","x":150,"y":28,"wires":[]},{"id":"16ef8ac1.29e885","type":"comment","z":"a4f46023.3fb8f","name":"Live Video","info":"","x":100,"y":133,"wires":[]},{"id":"1eb743e.b3a92bc","type":"comment","z":"a4f46023.3fb8f","name":"Latest Alarm Snap","info":"","x":343,"y":235,"wires":[]},{"id":"3f193c7b.e47fd4","type":"comment","z":"a4f46023.3fb8f","name":"Catch Alarm Events","info":"","x":129,"y":238,"wires":[]},{"id":"5909c1f5.4347a","type":"comment","z":"a4f46023.3fb8f","name":"Display Historic Event","info":"","x":860,"y":388,"wires":[]},{"id":"fbc900bc.b83bb","type":"mqtt-broker","z":"","name":"192.168.2.117","broker":"192.168.2.117","port":"8883","tls":"70b691b5.003e5","clientid":"nodered","usetls":true,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"95e63e10.d8989","type":"ui_group","z":"","name":"History","tab":"be32d183.f5138","order":4,"disp":true,"width":"12","collapse":false},{"id":"ae53a8a2.0e42c8","type":"ui_group","z":"","name":"Latest Alarm","tab":"be32d183.f5138","order":2,"disp":true,"width":"12","collapse":false},{"id":"b022415d.c9b4a","type":"ui_group","z":"","name":"Live Video","tab":"be32d183.f5138","order":1,"disp":true,"width":"12","collapse":false},{"id":"70b691b5.003e5","type":"tls-config","z":"","name":"8015Black","cert":"","key":"","ca":"","certname":"pcert.pem","keyname":"","caname":"","servername":"","verifyservercert":false},{"id":"be32d183.f5138","type":"ui_tab","z":"","name":"IN-8015black","icon":"camera","disabled":false,"hidden":false}]

Walkthrough

  1. Create a database in AlaSQL (I called mine AlarmSnapBlack as I have both a white and a black version of the INSTAR IN-8015 Full HD MQTT camera ~). This node is triggered with every restart of Node-RED to make sure that it is ready for use.

  2. Fetch the live video (I am using ONVIF you can use another way to embed the live video)

  3. Now I need an MQTT IN node that is listening to my cameras alarmserver instar/local/status/alarm/triggered (see this posting for an explanation how those topics work)

  4. The alarm server node triggers Node-RED to fetch a snapshot from my camera, base64 it, and stores it into the database. Additionally, I display the snapshot on my dashboard. I added a trigger node that makes sure that there is only one image per alarm event - you might want to remove that or adjust the 8 second cool down that I found works perfectly for my use case.

  5. The alarm server node also triggers the Timeline to register an event.

  6. Node-RED receives the timestamp of an event when you click on it in the timeline dashboard widget. I use this timestamp to search for the corresponding event in my database and display the corresponding image on my dashboard. Note that the timestamp format used inside the database and the one used by the timeline widget is different. To be able to compare them I am using the Moment.js - you will have to configure this one according to your system timezone settings!

Node-RED Dashboard

  1. Top left is the live video from my camera

  2. Top right is the snapshot from the latest alarm event

  3. Bottom left - clicking on an event inside the timeline widget loads the corresponding alarm snapshot.

3 Likes

@afelix This is the flow that I was working on - AlaSQL now works like a charm.

@dceejay Thank you for recommending ui-state-trail - exactly what I was looking for :slightly_smiling_face:

1 Like

The cats triggering the alarm events somehow made this even better. Nice work! :+1:

1 Like

Some helpful debugging nodes:

[{"id":"dea87460.26ba98","type":"inject","z":"a4f46023.3fb8f","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":620,"wires":[["34317a97.c520b6"]]},{"id":"34317a97.c520b6","type":"alasql","z":"a4f46023.3fb8f","name":"SELECT","query":"SELECT * FROM AlarmSnapBlack","x":257,"y":620,"wires":[["393c61a8.10f7be"]]},{"id":"393c61a8.10f7be","type":"debug","z":"a4f46023.3fb8f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":410,"y":620,"wires":[]},{"id":"62a5ccdf.eefcf4","type":"inject","z":"a4f46023.3fb8f","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":675,"wires":[["f511d95d.1ba208"]]},{"id":"f511d95d.1ba208","type":"alasql","z":"a4f46023.3fb8f","name":"DROP","query":"DROP TABLE AlarmSnapBlack","x":257,"y":675,"wires":[[]]},{"id":"de1a7493.7e3b68","type":"inject","z":"a4f46023.3fb8f","name":"20 Min Trigger","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":735,"wires":[[]]},{"id":"5d77daed.9b4d44","type":"alasql","z":"a4f46023.3fb8f","name":"AlarmSnapBlack","query":"SELECT * FROM AlarmSnapBlack;","x":318,"y":740,"wires":[["30386b67.7ad104"]]},{"id":"30386b67.7ad104","type":"alafile out","z":"a4f46023.3fb8f","name":"json","filename":"/home/pi/.node-red/sql/AlarmSnapBlack","format":"json","columns":"*","headers":true,"x":490,"y":740,"wires":[]}]
  1. Show all database entries
  2. Drop database
  3. Backup database to file

This topic was automatically closed after 60 days. New replies are no longer allowed.