String vs Object with CronPlus

Hi All,

I have a cron-plus node that gets sunset and sunrise time and stores it into a global variable.

It has been causing me issues for months now, every time I stop and start the NR service I have to manually re-run the CRON job else I get an error when reading the stored value in the global variable.

This made no sense to me, but no matter how much I debugged it I couldn't figure out the problem.

After more time spent on it, I got one step closer. I found that even though the value of the property in my Global is identical before and after restarting NR, the code would not run because on restart the "type" of the property in the Global JSON changes from an "object" to a "string".

I have pasted the code from a mini-made-up flow below. You need cron-plus installed to run it...

[
    {
        "id": "651f808f980c783e",
        "type": "cronplus",
        "z": "1b60fd7bbe7aa8e7",
        "name": "",
        "outputField": "payload",
        "timeZone": "Europe/London",
        "persistDynamic": false,
        "commandResponseMsgOutput": "output1",
        "outputs": 1,
        "options": [
            {
                "name": "schedule1",
                "topic": "solarEvents",
                "payloadType": "default",
                "payload": "",
                "expressionType": "solar",
                "expression": "0 * * * * * *",
                "location": "51.434320273775626 -0.1318359375",
                "offset": "0",
                "solarType": "selected",
                "solarEvents": "sunrise,sunset"
            }
        ],
        "x": 1680,
        "y": 280,
        "wires": [
            [
                "a01603c461b77e06"
            ]
        ]
    },
    {
        "id": "a01603c461b77e06",
        "type": "function",
        "z": "1b60fd7bbe7aa8e7",
        "name": "Run first",
        "func": "let lighting_system = {\n    \"sunset\"                            : null,\n    \"sunrise\"                           : null,\n}\nglobal.set(\"AB_system_lighting111\",lighting_system);\n\nlet system = global.get(\"AB_system_lighting111\")\n\nvar sunrise = msg.payload.status.solarTimes.find(_=>_.event == \"sunrise\");\nvar sunset  = msg.payload.status.solarTimes.find(_=>_.event == \"sunset\");\n\nsystem.sunrise = sunrise.time\nsystem.sunset = sunset.time\n\nlet type = typeof system.sunrise\nlet type1 = typeof system.sunset\n\n node.warn([\"type output\",type, type] )",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1840,
        "y": 280,
        "wires": [
            []
        ]
    }
]
[
    {
        "id": "131bd77c2138a20d",
        "type": "cronplus",
        "z": "1b60fd7bbe7aa8e7",
        "name": "",
        "outputField": "payload",
        "timeZone": "Europe/London",
        "persistDynamic": false,
        "commandResponseMsgOutput": "output1",
        "outputs": 1,
        "options": [
            {
                "name": "schedule1",
                "topic": "solarEvents",
                "payloadType": "default",
                "payload": "",
                "expressionType": "solar",
                "expression": "0 * * * * * *",
                "location": "51.434320273775626 -0.1318359375",
                "offset": "0",
                "solarType": "selected",
                "solarEvents": "sunrise,sunset"
            }
        ],
        "x": 1680,
        "y": 340,
        "wires": [
            [
                "a0782992a46edfee"
            ]
        ]
    },
    {
        "id": "a0782992a46edfee",
        "type": "function",
        "z": "1b60fd7bbe7aa8e7",
        "name": "Run before and after restart",
        "func": "let system = global.get(\"AB_system_lighting111\")\n\nlet type = typeof system.sunrise\nlet type1 = typeof system.sunset\n\n node.warn([\"type output\",type, type] )",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1900,
        "y": 340,
        "wires": [
            []
        ]
    }
]
  1. run the first flow, titled: "run first"
  2. run the second flow titled: "run before and after restart" -- to see that the properties are still objects 3. stop and start node-red
  3. run the second one titled: "run before and after restart" -- to see that the properties have now changed to strings!

I am running:
Cron-plus 1.5.7
NR 3.0.0

Edit: you need to be saving to disc for the data to persist and the above test to work

1 Like

As per your last post... to be clear, the issue only arises when your flows are edited to save & retrieve context from the file system.

After those changes..

  1. Flow No1 saves it's data to file context
    context

  2. Flow No2 is run which gives a payload of;
    object1

  3. Stop & restart node-RED

  4. Run Flow No2 again, and this time it outputs;
    object2

Checking the context in the sidebar, the saved object remains unchanged after a node-RED restart (as expected).

Flow No2 just retrieves the context object, and uses typeof to determine data type -

let system = global.get("AB_system_lighting111", 'Disk')
let type = typeof system.sunrise
let type1 = typeof system.sunset
node.warn(["type output",type, type1] )

I have no idea why the 'typeof' changes from object to string after a restart, but I thought I'd expand on your post above.

I'll tag @Steve-Mcl for his info

2 Likes

@Paul-Reed, thank you for doing what I should have the first time with screenshots!

Much easier to understand, and glad you have been able to reproduce it.

What's also annoying is that I can't seem to figure out how to convert the string back to an object... I have tried the standard JS JSON commands.

2 Likes

To save things in context they need to be serialised. Date objects do not get serialised well by javascript and end up stored as strings. You should be able to use the Date() function to convert them back.

2 Likes

In that case, how does step 2 (above) retrieve the saved context, and describes it as an object?
It's only after a node-RED re-start that step 4 describes it as a string.

1 Like

Because it’s still in memory cache at that point.

3 Likes

Ok, so there is no actual fix for this then?

As a workaround, I added an inject node on start-up to retrieve the values from the global, they are stored in, and update the global variable after passing the value through new Date(), and the issue has gone away:

global_variable = new Date([global_variable])

Thanks @dceejay!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.