External BackUp global data Node Red

Yesterday suddenly the Cerbo deleted all my global data while this is saved to the local filesystem (via node persistent-global-context).

What happend, no idea but it happend after a deploy. All flows where still there and started up again.

My question now is how can I make automatic form time to time backup this data to for example a USB stick plugged in the Cerbo.

Idea would be using the write file node but what kind off path should be used to get to a USB stick in the Cerbo?

Just to avoid loosing again my data.

Well node-red has nodes to create/read/write files on a specific path. I use it all the time to create and handle “ultra-remanent” and “file system compatible” parameters. Best to browse for the path in the linux terminal over SSH for example.

@VerreyckenGerd

I found that in mean time also :slight_smile: ; I managed to write to the SD card off the cerbo. Then I can via winSCP copy those files.

But what i could not find is how I have to construct the write path to a network pc (for example my pc).

I made a shared map but if is use that path it doesn’t work.

example → //DESKTOP/Users/Stefan/Documents/Data/CerboData

BUT better would be if I could write to USB stick in the Cerbo once a while making backup and store the stick on safe place. Here the same question what path structure is needed to write to the USB? Tried already with the name off the stick but didn’t worked.

Maby you can write a function in Node-Red to send all parameters to an MQTT broker in your network and run a node-red on your windows/mac PC to receive and wrte to your USB stick. This way you can stay behind your desktop pc to make the backup. Thinking a bit out of the box here….

Was also thinking about something like that but that is ok for myself but not for installations at customers or installations with limited internetconnection (4G routers).

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:

  • Power interruptions

  • Unexpected system behavior

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"
            ]
        ]
    }]