Check current dynamic IP then store it if it's new or update timestamp if it's not

My in-laws are on DHCP, with their ISP but they occasionally need me to administer their OpenSuse desktop (yes, they’re on Linux). It takes several tens of minutes, every time to get them to find and reveal their WAN IP. As a result, I’m using a Raspberry Pi zero W, piggbacking their router, powered by its USB. Currently I’ve got a shell script run by cron every 5 minutes. It works but I’m using mailx to send me the IP, if it’s updated but Google have made life much more difficult, so I’m trying Node-Red & MQTT to try and achieve similar results but more efficiently.

So far I’ve used curl 'http://api.ipify.org/' in an exec node to periodically return my WAN IP and store it. Now I want to compare it to a file (csv, json, text?), to see if this IP is the same as the one on the last line. If it is, then I want to update the timestamp (first seen & last seen) on the last line of the file. If it’s not, I want to append the IP & timestamp to the file. Finally, I’ll use mqtt to also log the IP with the time it was first seen and the time it was last seen on my server.

I can’t see a Node-Red way to check the last row of the file and compare it to the msg.payload of my IP check, then take one of the two actions accordingly (update timestamp or append a new line)

Can anyone suggest a good pattern to follow with Node-Red?

To get the ip you could also use the http request node:

[{"id":"df8b7ad8.2a01c8","type":"http request","z":"cf6706ec.415a58","name":"","method":"GET","ret":"txt","url":"","tls":"","x":510,"y":1040,"wires":[["95331697.0175f8"]]},{"id":"95331697.0175f8","type":"debug","z":"cf6706ec.415a58","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":670,"y":1040,"wires":[]},{"id":"46f1b0ad.fbc45","type":"change","z":"cf6706ec.415a58","name":"","rules":[{"t":"set","p":"url","pt":"msg","to":"topic","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":1040,"wires":[["df8b7ad8.2a01c8"]]},{"id":"a150a92a.c7f9d8","type":"inject","z":"cf6706ec.415a58","name":"http://api.ipify.org/","topic":"http://api.ipify.org/","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":1040,"wires":[["46f1b0ad.fbc45"]]}]

To store and compare the ip you might use a flow/global variable and the contrib-persist node:

Why not set them up with a bookmark to go to ipchicken.com? Or that ‘that’ bad with computers (I had an aunt who, when I told her to enter aomething in the address bar, would open up google and enter it into the search field and swear that is whaat I showed her to do)

Hi,

I’ve loaded in the flow and also installed the persistent node, but I can’t work out how to make it all work together so it automatically checks is the new ip is different to the old one ?

Do you have an example you could share ?

That is an old/obsolete post!

I'm now using a dsm-node to check the ip:

[{"id":"b57294a9.594a58","type":"dsm","z":"85cbcdb3.ef251","name":"read ip","sm_config":"{\n    \"currentState\": \"idle\",\n    \"states\": {\n        \"idle\": {\n            \"read\": \"reading\"\n        },\n        \"reading\": {\n            \"evaluade\": \"idle\"\n        }\n    },\n    \"data\": {\n        \"url\": \"http://api.ipify.org/\",\n        \"ip\": null\n    },\n    \"methods\": {\n\t\t\"init\": [\n\t\t    \"sm.request = require('request');\"\n\t\t],\n        \"read\": [\n            \"sm.request.get({url: sm.data.url}, function(error, response, body) {\",\n            \"   if (!error && response.statusCode == 200) {\",\n            \"       sm.data.current_ip = body;\",\n            \"       resume('evaluade', msg);\",\n            \"   } else {\",\n            \"       node.warn(error);\",\n            \"       sm.text = error.message;\",\n            \"       sm.fill = 'red';\",\n            \"   }\",\n            \"});\",\n            \"sm.text = 'reading';\",\n            \"output = false;\"\n        ],\n        \"evaluade\": [\n            \"if (sm.data.current_ip !== sm.data.ip) {\",\n            \"   sm.data.ip = sm.data.current_ip;\",\n            \"   msg.payload = sm.data.ip;\",\n            \"   node.send(msg);\",\n            \"   sm.text = 'new ip='+sm.data.ip;\",\n            \"   sm.fill = 'yellow';\",\n            \"} else {\",\n            \"   sm.text = 'unchanged ip='+sm.data.ip;\",\n            \"   sm.fill = 'green';\",\n            \"}\",\n            \"output = false;\"\n        ],\n        \"status\": {\n            \"fill\": {\n                \"get\": \"sm.fill || 'blue';\"\n            },\n            \"shape\": \"dot\",\n            \"text\": {\n                \"get\": \"sm.text || 'dummy';\"\n            }\n        }\n    }\n}\n","x":240,"y":1800,"wires":[["7027ffea.5623a"]]},{"id":"7027ffea.5623a","type":"debug","z":"85cbcdb3.ef251","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":430,"y":1800,"wires":[]},{"id":"534650cf.6a398","type":"inject","z":"85cbcdb3.ef251","name":"","topic":"read","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":1800,"wires":[["b57294a9.594a58"]]}]
1 Like

Thanks @cflurin

I’ve just imported your flow but when I click to inject I receive the following error?

23/10/2018, 10:48:46 node: read ip
msg : error
"TypeError: Cannot read property 'get' of undefined"

Any ideas ?

Which versions are you using?

node-red
node.js
node-red-contrib-dsm

operating system: raspbian or ?

It’s a docker so not sure of all the specifics, or node.js part; as for the others - here you go..

Node red v 0.19.2
node-red-contrib-dsm v0.11.0 (there does look to be a newer version available)?

Other dsm seem to be working fine, if that helps ?

Install the latest node-red-contrib-dsm version, 0.12.2

Ok, that seems to have done the trick - thanks @cflurin

You can also consider using dynamic DNS so that you can access OpenSuse desktop from the internet using a hostname instead of an IP address.

For this you need to register the hostname with a dynamic DNS provider (some are free).

Some internet routers are able to automatically update the IP address linked to your hostname through the dynamic DNS providers API. If your router is not able to do this then you can create simple node-red flow running on OpenSuse desktop that does this (or I can share it if you want).

It would be interesting to see it but I want to run it on a pi zero, powered from the router's USB, so that it's always on, not just when the olds power up their desktop.

Of course you can also run the node-red flow on a pi zero.

Here below the flow I am using that checks my public IP address and if it has changed then it will update it (I am using changeip.com as DDNS provider).

[{"id":"4579ebe6.c7c454","type":"inject","z":"8a9419fa.8029f8","name":"Get IP","topic":"ip","payload":"","payloadType":"str","repeat":"21600","crontab":"","once":true,"onceDelay":"5","x":120,"y":260,"wires":[["91eb23ac.09af1"]]},{"id":"91eb23ac.09af1","type":"exec","z":"8a9419fa.8029f8","command":"wget -qO- http://ipv4bot.whatismyipaddress.com/ ; echo","addpay":false,"append":"","useSpawn":"","timer":"","oldrc":false,"name":"Call IP - whatismyipaddress.com","x":343,"y":259.5,"wires":[["a8fa9540.151a28","dcaf176b.4ff1e8"],["362d622c.c0556e"],["90de538b.37c7d"]]},{"id":"a8fa9540.151a28","type":"switch","z":"8a9419fa.8029f8","name":"Integrity check","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b","vt":"str","case":false},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":620,"y":220,"wires":[["cbbbb8d6.385c08","2bca8b86.a2ac04"],[]]},{"id":"dcaf176b.4ff1e8","type":"debug","z":"8a9419fa.8029f8","name":"call ip : stdout","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":620,"y":180,"wires":[]},{"id":"3c25c3e5.9d336c","type":"comment","z":"8a9419fa.8029f8","name":"Flow Documentation","info":"The first part of the flow is based on : https://flows.nodered.org/flow/9559f217b08913702c38\nFor more information about the service used see: https://whatismyipaddress.com/api\n\nThe second part of the flow will update the IP address if changed.\nSee also https://discourse.nodered.org/t/how-to-change-dynamic-ip-address-changip-com-using-node-red/4164/2","x":110,"y":40,"wires":[]},{"id":"362d622c.c0556e","type":"debug","z":"8a9419fa.8029f8","name":"call ip - stderr","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":620,"y":280,"wires":[]},{"id":"90de538b.37c7d","type":"debug","z":"8a9419fa.8029f8","name":"call ip - return code","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":630,"y":340,"wires":[]},{"id":"cbbbb8d6.385c08","type":"debug","z":"8a9419fa.8029f8","name":"my public ip address","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":880,"y":220,"wires":[]},{"id":"eb78fdb1.d7aa5","type":"http request","z":"8a9419fa.8029f8","name":"","method":"GET","ret":"txt","url":"","tls":"","x":550,"y":520,"wires":[["76ea2460.c2552c","60e58409.43daac"]]},{"id":"76ea2460.c2552c","type":"debug","z":"8a9419fa.8029f8","name":"http change ip address response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":790,"y":520,"wires":[]},{"id":"1612e69b.6d5af9","type":"switch","z":"8a9419fa.8029f8","name":"flow.public_ip != msg.payload ?","property":"payload","propertyType":"msg","rules":[{"t":"neq","v":"public_ip","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":910,"y":380,"wires":[["5a4d1631.39cee8"]]},{"id":"7a98abc4.d1f654","type":"change","z":"8a9419fa.8029f8","name":"flow.public_ip = msg.payload","rules":[{"t":"set","p":"public_ip","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":440,"wires":[["3c7c8a4f.611bf6","c2d5215e.3d9ee"]]},{"id":"3c7c8a4f.611bf6","type":"debug","z":"8a9419fa.8029f8","name":"new public IP address","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":1180,"y":440,"wires":[]},{"id":"5f11d6ed.ab5d48","type":"inject","z":"8a9419fa.8029f8","name":"On Start","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":120,"y":100,"wires":[["29093c22.2201a4"]]},{"id":"c2d5215e.3d9ee","type":"change","z":"8a9419fa.8029f8","name":"set msg.url for changeip","rules":[{"t":"set","p":"url","pt":"msg","to":"$flowContext(\"changeip_url\") & \"&myip=\" & $flowContext(\"public_ip\") & \"&\" & $flowContext(\"changeip_url_credentials\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":520,"wires":[["eb78fdb1.d7aa5"]]},{"id":"2bca8b86.a2ac04","type":"change","z":"8a9419fa.8029f8","name":"remove last char","rules":[{"t":"set","p":"payload","pt":"msg","to":"$substring(payload, 0, $length(payload)-1)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":320,"wires":[["1612e69b.6d5af9"]]},{"id":"24e1af82.6d37","type":"inject","z":"8a9419fa.8029f8","name":"set test ip address","topic":"","payload":"9.9.9.9","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":600,"wires":[["8601775f.9f4518"]]},{"id":"8601775f.9f4518","type":"change","z":"8a9419fa.8029f8","name":"flow.public_p = msg.payload","rules":[{"t":"set","p":"public_ip","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":600,"wires":[[]]},{"id":"5a4d1631.39cee8","type":"change","z":"8a9419fa.8029f8","name":"set tweet msg","rules":[{"t":"set","p":"tweet","pt":"msg","to":"\"old public IP=\" & $flowContext(\"public_ip\")\t& \", new public IP=\" & payload &\t\"\\n changing public IP address ...\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1160,"y":360,"wires":[["7a98abc4.d1f654","4dd4e24b.4618cc"]]},{"id":"c069b3e7.42cd8","type":"change","z":"8a9419fa.8029f8","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"public IP address successfully changed\"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":560,"wires":[["d4a77d7d.0c103"]]},{"id":"60e58409.43daac","type":"switch","z":"8a9419fa.8029f8","name":"statusCode ?","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":710,"y":580,"wires":[["c069b3e7.42cd8"],["a7baeff0.4920b"]]},{"id":"a7baeff0.4920b","type":"change","z":"8a9419fa.8029f8","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"Failed to change public IP address (StatusCode = \" &\tpayload & \")\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":600,"wires":[["d4a77d7d.0c103"]]},{"id":"4dd4e24b.4618cc","type":"change","z":"8a9419fa.8029f8","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"tweet","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1160,"y":520,"wires":[["d4a77d7d.0c103"]]},{"id":"29093c22.2201a4","type":"credentials","z":"8a9419fa.8029f8","name":"set changeip credentials in flow context","props":[{"value":"changeip_url_credentials","type":"flow"},{"value":"changeip_url","type":"flow"}],"x":400,"y":100,"wires":[[]]},{"id":"d4a77d7d.0c103","type":"debug","z":"8a9419fa.8029f8","name":"Output Message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":1220,"y":600,"wires":[]}]

Note that the 2 credentials in the credential node are set as follows:

  • changeip_url_credentials = u=XXXX&p=YYYY
  • changeip_url = https://nic.changeip.com/nic/update?hostname=bXXXXX.onmypc.org

Here's the solution I came up with:

  • It runs on a pi zero W, powered by the router's USB port.
  • Wireless IP is set to dynamic
  • I have MQTT (Mosquitto) running on an always on pi that is accessible through my firewall
  • Every five minutes it checks the WAN IP. If it's new, it appends a row to an sqlite3 DB and MQTT publishes the IP & Time to topic IP/HOSTNAME on the MQTT server. It also writes the IP & human readable date to a 4 line LCD
  • I haven't yet tried getting an MQTT client on my Android phone, subscribing to the topic IP/HOSTNAME, but it works when I run a client on my desktop/laptop.

[{"id":"aa0f87db.45865","type":"inject","z":"1853a7d0.062638","name":"","topic":"","payload":"","payloadType":"date","repeat":"300","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":160,"wires":[["e755640a.6d17d","f1ffdff2.696fc"]]},{"id":"365ac05a.a28b3","type":"http request","z":"1853a7d0.062638","name":"Get current WAN IP","method":"GET","ret":"txt","url":"http://api.ipify.org/","tls":"","x":330,"y":220,"wires":[["29f04258.fed3e6","9ae8d51c.424a8"]]},{"id":"9ae8d51c.424a8","type":"function","z":"1853a7d0.062638","name":"Store IP in flow variable","func":"//store IP in a flow scoped variable\nvar flowIP;\n\nflow.set(\"flowIP\", msg.payload);\n\nmsg.ip = msg.payload;\n\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":280,"wires":[["dec2afa5.4d27","f2bac181.2a91d","395f8fcf.dbabd8"]]},{"id":"e755640a.6d17d","type":"function","z":"1853a7d0.062638","name":"Get current time","func":"//store now-time in a flow scoped variable\nvar justSeen, justSeenUTC;\n\nflow.set(\"now_time\", msg.payload);\n\nmsg.justSeen = msg.payload;\n\n//return msg;\n\n\n\n var date = new Date(msg.justSeen);\n msg.justSeenUTC = date.toUTCString();\n return msg;\n\n\n\n//msg.justSeen = msg.payload;\n//return msg;","outputs":1,"noerr":0,"x":320,"y":180,"wires":[["a6539c2.5b98b6","365ac05a.a28b3"]]},{"id":"dec2afa5.4d27","type":"debug","z":"1853a7d0.062638","name":"IP","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":570,"y":240,"wires":[]},{"id":"f1ffdff2.696fc","type":"sqlite","z":"1853a7d0.062638","mydb":"7331442a.3f9e44","sqlquery":"fixed","sql":"CREATE TABLE IF NOT EXISTS IPlog (\n ID INTEGER PRIMARY KEY AUTOINCREMENT,\n currentIP TEXT,\n lastSeen TEXT,\n firstSeen TEXT\n);\n","name":"IPmonLog","x":310,"y":120,"wires":[["a77a977d.13d05"]],"outputLabels":["Table created?"]},{"id":"de03078c.76de5","type":"sqlite","z":"1853a7d0.062638","mydb":"7331442a.3f9e44","sqlquery":"msg.topic","sql":"\n\n","name":"Insert or update IP","x":770,"y":440,"wires":[["3ad79da0.9e81aa","d52074d4.e6d8d8"]]},{"id":"f2bac181.2a91d","type":"sqlite","z":"1853a7d0.062638","mydb":"7331442a.3f9e44","sqlquery":"fixed","sql":"SELECT currentIP as lastRecordedIP from IPlog WHERE ID = (SELECT MAX(ID) FROM IPlog);","name":"Query content","x":320,"y":340,"wires":[["5b9458c5.c189b","a46b38d0.9c6e9"]]},{"id":"5b9458c5.c189b","type":"debug","z":"1853a7d0.062638","name":"Get last row currentIP","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":620,"y":340,"wires":[]},{"id":"29f04258.fed3e6","type":"debug","z":"1853a7d0.062638","name":"WAN-IP","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":740,"y":220,"wires":[]},{"id":"a77a977d.13d05","type":"debug","z":"1853a7d0.062638","name":"Create table","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":590,"y":120,"wires":[]},{"id":"a46b38d0.9c6e9","type":"function","z":"1853a7d0.062638","name":"SQL to Insert or update a row","func":"var lastRecordedIP = \"0.0.0.0\";\nif (msg.payload.length !== 0) { \n lastRecordedIP = msg.payload[0].lastRecordedIP;\n}\n//msg.ip = msg.payload[0].lastRecordedIP;\nif (lastRecordedIP == msg.ip) {\n msg.topic = \"UPDATE IPlog \" +\n \"SET lastSeen = \" + msg.justSeen +\n \" WHERE ID = (SELECT MAX(ID) FROM IPlog);\"\n return [msg,null];\n} else {\n msg.topic = \"INSERT INTO IPlog (currentIP, firstSeen, lastSeen) \" + \n \"VALUES ('\" + msg.ip + \"','\" + msg.justSeen + \"','\" + msg.justSeen + \"') ;\";\n mqttMessage = \"'\" + msg.ip + \"','\" + msg.justSeen + \"','\" + msg.justSeen + \"'\";\n msg.payload = { mqttMessage };\n return [msg, {payload:msg.justSeen}];\n //return [msg, msg];\n}\nreturn null;\n//return [msg, {payload:msg.justSeen}];","outputs":2,"noerr":0,"x":370,"y":400,"wires":[["de03078c.76de5","2a091618.2c6caa"],["48de3bfc.cac084","58c28bf2.4ac2cc"]]},{"id":"3ad79da0.9e81aa","type":"sqlite","z":"1853a7d0.062638","mydb":"7331442a.3f9e44","sqlquery":"fixed","sql":"SELECT * from IPlog ;","name":"Query ALL content","x":770,"y":500,"wires":[["1d19f479.5960e4"]]},{"id":"1d19f479.5960e4","type":"debug","z":"1853a7d0.062638","name":"Get last row currentIP","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":1000,"y":500,"wires":[]},{"id":"d52074d4.e6d8d8","type":"debug","z":"1853a7d0.062638","name":"SQL summary","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":980,"y":440,"wires":[]},{"id":"e4a6e881.f197e8","type":"comment","z":"1853a7d0.062638","name":"SQLite IPmonLog schema","info":"CREATE TABLE IPlog (\nID INTEGER PRIMARY KEY AUTOINCREMENT,\ncurrentIP TEXT,\nlastSeen TEXT,\nfirstSeen TEXT\n);","x":350,"y":60,"wires":[]},{"id":"69785ef5.bf0e5","type":"mqtt out","z":"1853a7d0.062638","name":"Publish new IP","topic":"IP/INDIAN","qos":"0","retain":"false","broker":"9a1f4bd8.e4b938","x":320,"y":580,"wires":[]},{"id":"48de3bfc.cac084","type":"debug","z":"1853a7d0.062638","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":570,"y":460,"wires":[]},{"id":"a6539c2.5b98b6","type":"debug","z":"1853a7d0.062638","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":570,"y":180,"wires":[]},{"id":"a9f70c33.1d0f58","type":"debug","z":"1853a7d0.062638","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":570,"y":540,"wires":[]},{"id":"7d60391b.c4eba","type":"function","z":"1853a7d0.062638","name":"Date object to string","func":"msg.justSeenString = msg.justSeen.toISOString();\n/* msg.justSeen.years.toString() + \"-\" +\n msg.justSeen.months.toString() + \"-\" + \n msg.justSeen.date.toString() + \"_\" + \n msg.justSeen.hours.toString() + \":\" + \n msg.justSeen.minutes.toString() + \":\" +\n msg.justSeen.seconds.toString() + \".\" + \n msg.justSeen.milliseconds.toString();\n*/ \nreturn msg;","outputs":1,"noerr":0,"x":620,"y":60,"wires":[[]]},{"id":"bac5770e.fd6a08","type":"function","z":"1853a7d0.062638","name":"Add IP to report string","func":"msg.mqttString = flow.get(\"flowIP\") + \" - First detected: \" + msg.payload;\nmsg.payload = msg.mqttString;\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":520,"wires":[["a9f70c33.1d0f58","69785ef5.bf0e5"]]},{"id":"2a091618.2c6caa","type":"debug","z":"1853a7d0.062638","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":730,"y":380,"wires":[]},{"id":"824480a1.3ec35","type":"debug","z":"1853a7d0.062638","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":570,"y":500,"wires":[]},{"id":"58c28bf2.4ac2cc","type":"moment","z":"1853a7d0.062638","name":"","topic":"","input":"payload","format":"YYYY-MM-DD HH:mm:ss","locale":"en_GB","output":"payload","x":340,"y":460,"wires":[["bac5770e.fd6a08","824480a1.3ec35"]]},{"id":"395f8fcf.dbabd8","type":"moment","z":"1853a7d0.062638","name":"","topic":"","input":"justSeenUTC","format":"DD/MM/YY HH:mm","locale":"en_GB","output":"payload","fakeUTC":false,"x":620,"y":280,"wires":[["fd1fd35b.f9f02","d5b042ad.00327"]]},{"id":"cf2067f8.51fc78","type":"rpi-lcd","z":"1853a7d0.062638","name":"Current IP -line 1","pins":"37,35,33,31,29,22","x":1090,"y":280,"wires":[]},{"id":"fd1fd35b.f9f02","type":"function","z":"1853a7d0.062638","name":"Display IP","func":"var displayString;\ndisplayString2 = flow.get(\"flowIP\");\nmsg.payload = displayString2;\nreturn msg;","outputs":1,"noerr":0,"x":840,"y":280,"wires":[["cf2067f8.51fc78","2878b2bf.68c4c6"]]},{"id":"2878b2bf.68c4c6","type":"debug","z":"1853a7d0.062638","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1070,"y":240,"wires":[]},{"id":"d5b042ad.00327","type":"function","z":"1853a7d0.062638","name":"Display just seen","func":"msg.payload = \"2:\" + msg.payload\nreturn msg;","outputs":1,"noerr":0,"x":870,"y":320,"wires":[["cf2067f8.51fc78"]]},{"id":"7331442a.3f9e44","type":"sqlitedb","z":"","db":"/home/pi/IPmonLog.db","mode":"RWC"},{"id":"9a1f4bd8.e4b938","type":"mqtt-broker","z":"","name":"DriveCam","broker":"212.159.76.88","port":"11883","tls":"","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

You'd need to:

Install SQLite3 and create a DB, called IPmonLog.db
Install the Node-Red SQLite node in the palette
If you want the LCD, install the Node-Red GPIO and LCD nodes into the palette

I have a monitor that subscribes to my Mum's WAN IP and another subscription for my in-law's WAN IP

The audio is optional, obvs.
[{"id":"e49d3f3a.bfccb8","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"cd19e722.e299a8","type":"mqtt in","z":"e49d3f3a.bfccb8","name":"","topic":"IP/INDIAN","qos":"2","broker":"90ae653c.70b1b8","x":320,"y":100,"wires":[["16c1e9e0.49959e","221658d3.fe90f8","98c1607.04bed2"]]},{"id":"16c1e9e0.49959e","type":"debug","z":"e49d3f3a.bfccb8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":550,"y":100,"wires":[]},{"id":"28c3e2dc.6464fe","type":"mqtt in","z":"e49d3f3a.bfccb8","name":"","topic":"IP/ARCTIC","qos":"2","broker":"90ae653c.70b1b8","x":320,"y":240,"wires":[["eceb9588.c4a96","71995d30.3dcf7c","f0f1bcf4.43db88"]]},{"id":"eceb9588.c4a96","type":"debug","z":"e49d3f3a.bfccb8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":550,"y":240,"wires":[]},{"id":"221658d3.fe90f8","type":"play audio","z":"e49d3f3a.bfccb8","name":"","voice":"0","x":550,"y":140,"wires":[]},{"id":"71995d30.3dcf7c","type":"play audio","z":"e49d3f3a.bfccb8","name":"","voice":"1","x":550,"y":280,"wires":[]},{"id":"98c1607.04bed2","type":"function","z":"e49d3f3a.bfccb8","name":"e-mail INDIAN ","func":"msg.payload = \"INDIAN - \" + msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":560,"y":180,"wires":[["e602b4e9.5026e8"]]},{"id":"f0f1bcf4.43db88","type":"function","z":"e49d3f3a.bfccb8","name":"e-mail ARCTIC ","func":"msg.payload = \"ARCTIC - \" + msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":560,"y":320,"wires":[["e602b4e9.5026e8"]]},{"id":"e602b4e9.5026e8","type":"e-mail","z":"e49d3f3a.bfccb8","server":"smtp.gmail.com","port":"465","secure":true,"name":"","dname":"e-mail to me","x":850,"y":240,"wires":[]},{"id":"90ae653c.70b1b8","type":"mqtt-broker","z":"","name":"DriveCam","broker":"212.159.76.88","port":"11883","tls":"","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

Last thought. The code is pretty messy and I'm sure there are inconsistent methods for passing variables down the flow. Please feel free to respond with mods to tighten it up.