Hi,
Here is a simple Node-RED solution to backup and restore global data on a Cerbo GX using the local filesystem.
This flow periodically saves selected global variables into a JSON file stored on the Cerbo. In case of a Cerbo reboot or crash, the data is automatically restored at startup.
How it works
-
A timed inject node saves the required global data every X seconds
-
The data is written to a JSON file using the file node
-
The flow monitors the Cerbo uptime to detect a restart
-
When a restart is detected, the backup file is read
-
The data is validated (version check) and restored into global context
Storage location
The backup file is stored in: /data/home/nodered/
This path is persistent on Venus OS and survives reboots.
Why this approach :
Even when using persistent-global-context, global data can still be lost after:
This solution adds an additional safety layer and allows full control over what data is saved and restored.
You only need to add your own variables in the SAVE and RESTORE function nodes.
Best regards,
JMA
Flows-Backup-CerboGX.json.zip (4,4 Ko)
[{
"id": "fa703b5848f354f5",
"type": "file in",
"z": "94064ad791d4bacf",
"name": "BACKUP FLOW",
"filename": "/data/home/nodered/backup_historiques.json",
"filenameType": "str",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "utf8",
"allProps": false,
"x": 880,
"y": 280,
"wires": [
[
"057bd8cf7eeebb96"
]
]
},
{
"id": "6713965ad60157ed",
"type": "inject",
"z": "94064ad791d4bacf",
"name": "Backup",
"props": [],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "1",
"topic": "",
"x": 280,
"y": 280,
"wires": [
[
"199ed4187d5e0db8"
]
]
},
{
"id": "057bd8cf7eeebb96",
"type": "function",
"z": "94064ad791d4bacf",
"name": "BACKUP FLOW",
"func": "let backup;\n\ntry {\n backup = JSON.parse(msg.payload);\n} catch (e) {\n node.error(\"Backup JSON invalide\");\n node.status({ fill: \"red\", shape: \"ring\", text: \"Restore KO (JSON)\" });\n return null;\n}\n\nif (!backup || backup.version !== 1) {\n node.error(\"Backup incompatible\");\n node.status({ fill: \"red\", shape: \"ring\", text: \"Restore KO (version)\" });\n return null;\n}\n\n// sauvegarde du backup restauré dans le flow\nflow.set(\"backup_restored\", backup);\n\n// Status visuel\nnode.status({\n fill: \"green\",\n shape: \"dot\",\n text: `Restore OK`\n});\n\n// Réinjection dans le flow\nglobal.set(\"lastUptime\", backup.lastUptime || 0);\n\n// Your data here\n\n\nreturn null;\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 880,
"y": 360,
"wires": [
[]
]
},
{
"id": "e0b88205a3c46ad4",
"type": "inject",
"z": "94064ad791d4bacf",
"name": "Save - 1min",
"props": [],
"repeat": "60",
"crontab": "",
"once": true,
"onceDelay": "10",
"topic": "",
"x": 290,
"y": 200,
"wires": [
[
"5d40c5145449d65d"
]
]
},
{
"id": "5d40c5145449d65d",
"type": "function",
"z": "94064ad791d4bacf",
"name": "SAVE FLOW",
"func": "const now = new Date();\n\n// Création du backup\nconst backup = {\n version: 1,\n timestamp: Date.now(),\n lastUptime: global.get(\"lastUptime\") || 0\n \n // Your data here\n};\n\n// Sauvegarde dans le flow\nflow.set(\"backup_flow\", backup);\n\n// Status\nnode.status({\n fill: \"green\",\n shape: \"dot\",\n text: `Backup OK ${now.toLocaleTimeString('fr-FR')}`\n});\n\n// Envoi vers file out\nmsg.payload = backup;\n\nreturn msg;\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 470,
"y": 200,
"wires": [
[
"82836f3c73a9f954"
]
]
},
{
"id": "82836f3c73a9f954",
"type": "file",
"z": "94064ad791d4bacf",
"name": "SAVE FLOW",
"filename": "/data/home/nodered/backup_historiques.json",
"filenameType": "str",
"appendNewline": true,
"createDir": true,
"overwriteFile": "true",
"encoding": "utf8",
"x": 650,
"y": 200,
"wires": [
[
"a427e7458f5744c0"
]
]
},
{
"id": "a427e7458f5744c0",
"type": "function",
"z": "94064ad791d4bacf",
"name": "DEBUG",
"func": "node.warn(`✅ Fichier écrit ici : ${msg.filename}`);\nreturn null;\n",
"outputs": 0,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 860,
"y": 200,
"wires": []
},
{
"id": "199ed4187d5e0db8",
"type": "exec",
"z": "94064ad791d4bacf",
"command": "cat /proc/uptime",
"addpay": false,
"append": "",
"useSpawn": "false",
"timer": "",
"winHide": false,
"name": "Lecture UpTime",
"x": 460,
"y": 280,
"wires": [
[
"526f7ffe604a4277"
],
[],
[]
]
},
{
"id": "526f7ffe604a4277",
"type": "function",
"z": "94064ad791d4bacf",
"name": "UpTime processing",
"func": "// Extraction de l'uptime depuis la sortie exec\nlet match = msg.payload.match(/(\\d+)/);\n\nif (!match) {\n node.warn(\"Impossible de lire /System/Uptime\");\n return null;\n}\n\nlet uptime = parseInt(match[1], 10);\n\n// Récupère l'ancien uptime\nlet last = global.get(\"lastUptime\");\n\nmsg.REDEM = false;\n\nif (last !== undefined && uptime < last) {\n node.warn(\"Cerbo GX REDÉMARRÉ (uptime = \" + uptime + \" s)\");\n //\n msg.REDEM = true;\n //\n} else {\n node.warn(\"Uptime Cerbo GX = \" + uptime + \" s\");\n}\n\n// Sauvegarde pour la prochaine itération\nglobal.set(\"lastUptime\", uptime);\n\nif (!msg.REDEM) {\n return null; // RIEN ne sort, rien ne se lance\n}\n\nreturn msg; // là seulement la suite démarre\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 670,
"y": 280,
"wires": [
[
"fa703b5848f354f5"
]
]
}]