sonntam / node-red-contrib-xstate-machine

A xstate-based state machine implementation using state-machine-cat visualization for node red.
MIT License
22 stars 8 forks source link

Entry action not firing when deploying flows. #95

Open MRIIOT opened 11 months ago

MRIIOT commented 11 months ago

I have noticed behavior where the entry action does not get invoked upon deploying a NodeRed flow. This is demonstrated in the below flow 'test1'. The workaround is contained in 'test2' (image) where an intermediate, short-lived state is introduced.

image

[
    {
        "id": "ca58ed842a99f191",
        "type": "tab",
        "label": "xstate:unsettled",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "dcbedfe0f28bc94d",
        "type": "smxstate",
        "z": "ca58ed842a99f191",
        "name": "",
        "xstateDefinition": "// @ts-nocheck\nconst { assign } = xstate;\n\nconst sendInfo = (context, event, mta) => {\n  console.warn('sendinfo enter')\n\n  var metas = {\n    ...mta.state.meta[Object.keys(mta.state.meta)[0]],\n    ...mta.state.meta[Object.keys(mta.state.meta)[1]]\n  }\n\n  node.send({\n    topic: `${metas.topic}`,\n    payload: {\n      ts: Date.now(),\n      ...metas\n    }\n  });\n};\n\nreturn {\n  machine: {\n    id: 'test1',\n    meta: {\n      topic: 'test1'\n    },\n    context: {},\n    initial: 'unavailable',\n    states: {\n      unavailable: {\n        meta: {\n          value: 'UNAVAILABLE'\n        },\n        entry: ['sendInfo']\n      },\n      available: {\n        meta: {\n          value: 'AVAILABLE'\n        },\n        entry: ['sendInfo']\n      }\n    },\n    on: {\n      DEVICE_AVAILABILITY_AVAILABLE: '.available',\n      DEVICE_AVAILABILITY_UNAVAILABLE: '.unavailable'\n    }\n  },\n  config: {\n    guards: {\n\n    },\n    actions: {\n      sendInfo\n    },\n    activities: {\n\n    },\n    services: {\n\n    },\n    delays: {\n\n    }\n  }\n};",
        "noerr": 0,
        "x": 520,
        "y": 140,
        "wires": [
            [],
            [
                "3ef068dc37da38dc"
            ]
        ]
    },
    {
        "id": "b7fa3f877942edf3",
        "type": "inject",
        "z": "ca58ed842a99f191",
        "name": "DEVICE_AVAILABILITY_UNAVAILABLE",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "DEVICE_AVAILABILITY_UNAVAILABLE",
        "payload": "",
        "payloadType": "date",
        "x": 240,
        "y": 180,
        "wires": [
            [
                "dcbedfe0f28bc94d"
            ]
        ]
    },
    {
        "id": "8ee77e29746217f8",
        "type": "inject",
        "z": "ca58ed842a99f191",
        "name": "DEVICE_AVAILABILITY_AVAILABLE",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "DEVICE_AVAILABILITY_AVAILABLE",
        "payload": "",
        "payloadType": "date",
        "x": 230,
        "y": 120,
        "wires": [
            [
                "dcbedfe0f28bc94d"
            ]
        ]
    },
    {
        "id": "3ef068dc37da38dc",
        "type": "debug",
        "z": "ca58ed842a99f191",
        "name": "debug 64",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 760,
        "y": 140,
        "wires": []
    },
    {
        "id": "f81e76fe16d7f35e",
        "type": "comment",
        "z": "ca58ed842a99f191",
        "name": "test 1",
        "info": "",
        "x": 110,
        "y": 60,
        "wires": []
    },
    {
        "id": "f816d55d355a07e9",
        "type": "smxstate",
        "z": "ca58ed842a99f191",
        "name": "",
        "xstateDefinition": "// @ts-nocheck\nconst { assign } = xstate;\n\nconst sendInfo = (context, event, mta) => {\n  console.warn('sendinfo enter')\n\n  var metas = {\n    ...mta.state.meta[Object.keys(mta.state.meta)[0]],\n    ...mta.state.meta[Object.keys(mta.state.meta)[1]]\n  }\n\n  node.send({\n    topic: `${metas.topic}`,\n    payload: {\n      ts: Date.now(),\n      ...metas\n    }\n  });\n};\n\nreturn {\n  machine: {\n    id: 'test2',\n    meta: {\n      topic: 'test2'\n    },\n    context: {},\n    initial: 'unsettled',\n    states: {\n      unsettled: {\n        after: {\n          DELAY_SETTLE: {\n            target: 'unavailable'\n          }\n        }\n      },\n      unavailable: {\n        meta: {\n          value: 'UNAVAILABLE'\n        },\n        entry: ['sendInfo']\n      },\n      available: {\n        meta: {\n          value: 'AVAILABLE'\n        },\n        entry: ['sendInfo']\n      }\n    },\n    on: {\n      DEVICE_AVAILABILITY_AVAILABLE: '.available',\n      DEVICE_AVAILABILITY_UNAVAILABLE: '.unavailable'\n    }\n  },\n  config: {\n    guards: {\n\n    },\n    actions: {\n      sendInfo\n    },\n    activities: {\n\n    },\n    services: {\n\n    },\n    delays: {\n      DELAY_SETTLE: 1\n    }\n  }\n};",
        "noerr": 0,
        "x": 520,
        "y": 340,
        "wires": [
            [],
            [
                "9fbbb592c06d1d34"
            ]
        ]
    },
    {
        "id": "6a097a371c31dc50",
        "type": "inject",
        "z": "ca58ed842a99f191",
        "name": "DEVICE_AVAILABILITY_UNAVAILABLE",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "DEVICE_AVAILABILITY_UNAVAILABLE",
        "payload": "",
        "payloadType": "date",
        "x": 240,
        "y": 380,
        "wires": [
            [
                "f816d55d355a07e9"
            ]
        ]
    },
    {
        "id": "8ed17527886ce6b2",
        "type": "inject",
        "z": "ca58ed842a99f191",
        "name": "DEVICE_AVAILABILITY_AVAILABLE",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "DEVICE_AVAILABILITY_AVAILABLE",
        "payload": "",
        "payloadType": "date",
        "x": 230,
        "y": 320,
        "wires": [
            [
                "f816d55d355a07e9"
            ]
        ]
    },
    {
        "id": "9fbbb592c06d1d34",
        "type": "debug",
        "z": "ca58ed842a99f191",
        "name": "debug 65",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 760,
        "y": 340,
        "wires": []
    },
    {
        "id": "8f13198dfdc9668b",
        "type": "comment",
        "z": "ca58ed842a99f191",
        "name": "test 2",
        "info": "",
        "x": 110,
        "y": 260,
        "wires": []
    }
]
sonntam commented 11 months ago

Thank you for creating an issue. I haven't had any issues with the entry action not firing but I will definitely look into it. The example you provided will make it easier for me to debug.

You might have to give me some time for that as my lack of free time currently does not allow for spending a lot of time.

MRIIOT commented 11 months ago

Thanks, no rush. I lived with this issue for a while now.

sonntam commented 11 months ago

@MRIIOT I did some digging today. Your action does actually fire on deploy as well as on node red startup - you can see the text output sendinfo enter from your action in the command window.

But it seems to fire too early, i.e. the other nodes in the flow are not setup at this point so the message sent by node.send() seems to be lost. I'll have to find a way to defer the node.send() calls until the whole flow has been setup.

sonntam commented 11 months ago

I found the issue by comparing the flow of code to nr's core function node. They have added the possibility to add "setup" code that gets run when the node is deployed or when the flow is restarted. Calling node.send() from there just works. However that left be baffled for some time as it is extremely similar to xstate-machine.

The only functional difference I could find was that in the function node they do the setup within a promise. That way node's event pipe seems to allocate the task to a later point in time, at least after the node itself has been setup completely.

I incorporated the same promise usage into my code... voilá... it works (somehow). I'm not sure if this is a correct workaround and what side-effects it may have. I may have to do some more testing.

I'll add a commit soon which you could test for your case if you like.

sonntam commented 11 months ago

I was brave/stupid enough to push this directly to master as I can seem to see no problems. If you have git installed you can try by executing

npm install git://github.com/sonntam/node-red-contrib-xstate-machine.git#bb4ae00

in your nr user dir to use that specific version.

In order to go back just execute

npm install node-red-contrib-xstate-machine@1.3.0
sonntam commented 11 months ago

Sorry, had to fix the commit and its SHA.

sonntam commented 11 months ago

Release V1.3.1 should fix the problem. @MRIIOT Does it work for you now on 1.3.1?

MRIIOT commented 6 months ago

yes

MRIIOT commented 6 months ago

... but, the machine starts up very early. I have a trigger node (inject once after 0 seconds) that invokes a function node. In the function node I do flow.set('foo', 'bar_some_function'). In the xstate node I call flow.get('foo')() and the xstate node errors out "TypeError: flow.get(...) is not a function".

image

sonntam commented 6 months ago

Thanks! I‘ve been away for the past couple of days. I‘ll try to reproduce your problem and look into it!

sonntam commented 4 months ago

I couldn't reproduce your issue. I made a similar setup where I pass the output of the inject node to a function block that sets a var using flow.set("var", "something"). The output of the inject node is at the same time fed into xstate as an event where the action calls flow.get("var"). It works properly.

The problem you have might have to do with that the contents of your variable foo is not of function delegate/lambda type. Can you provide a minimal example that I can use?