Giter Site home page Giter Site logo

node-red-contrib-xstate-machine's Introduction

Node Red XState-Machine

Computing machines are like the wizards in fairy tales. They give you what you wish for, but do not tell you what that wish should be.
- Norbert Wiener, founder of cybernetics

The goal of this package is to provide an easy yet flexible and customizable way to define state-machines within node-red and effortlessly observe their state.

Changelog

See the separate changelog file for an overview over the available versions and changes.

Introduction

The idea for this package is based on node-red-contrib-finite-statemachine which was a very good starting point for me, but lacked some functionality that I wanted. If you only need to model simple state machines without guards, actions, time-based transitions, compund or parallel states this is the library you should go to!

This package provides state-machine functionality for node-red by utilizing the excellent xstate library as basis. It allows for very complex systems, e.g. nested machines, nested states, parallel states, history, deep history etc. For visualiztaion the library state-machine-cat comes into play.

The rest of the implementation is based on node-red's function and debug nodes.

Installation

  • Via node-red: Search for "node-red-contrib-xstate-machine" in the palette manager
  • Via terminal: Run npm install node-red-contrib-xstate-machine in your node-red user folder (typically ~/.node-red or %HOMEPATH%\.node-red)

Usage

Once this package is installed in node-red you will find two new items:

  • A new node named smxstate (shorthand for state-machine xstate)
  • A new sidebar (use the drop-down arrow) called State-machines for state-machine visualization

In the following the usage of both will be explained to get you started.

The smxstate node

Node image

Within this node you will find a javascript editor similar to the function-node. Here you can define your state-machine in a xstate-fashion. The javascript code must end with a return that returns an object of the following form:

return {
  machine: {
    ...
  },
  config: {
    ...
  }
}

The machine object is an xstate machine. For details on how to model or formulate your machine see the excellent xstate docs.

The config object contains all the named actions, activities, guards and services used in the machine object, see here.

See the node's help within node-red for further details: Node settings dialog

State-machines sidebar

The side bar allows for visualizing a machine instance's current data context and its state.

Visualization of example machine

There are a number of buttons available to control the editor view/control the machine:

  • Reveal button (the zoom out icon) for the current machine instance: This shows and highlights the currently chosen machine in a flow. If it is within a subflow it highlights the subflow's instance.
  • Reveal button (the zoom in icon) for prototype machines: If the machine instance runs within a subflow then the subflow is openend and the prototype node is highlighted.
  • Reset button: This resets the current machine instance to its initial state and data context.
  • Refresh graph button: This redraws the visualization manually (useful e.g. if the instance was changed on the server by another user or in a separate editor).

Also there are some settings that affect how the state graph is rendered.

  • With the Renderer: dropdown you can select the default smcat renderer. For more performance you can also use the native graphviz dot renderer if it is available on your system. In order to be able to select it the dot command must be available in the environment node-red is running in.
  • The Render timeout in ms: sets the maximum time a rendering may take to render. If you run on slow hardware you can crank up the value. Don't worry: The renderings are cached and only need to be redrawn if something changes. Else they are taken from the cache.

Example flows

Simple statemachine with data object

The follwing example is based on the example machine from state-machine-cat ๐Ÿ˜บ

In the image below you can see the node-red setup for the example.

Example flow

It tries to model a cat with 4 states

  • sleep
  • eat
  • play
  • meow

Events from the cat's point of view are

  • given food
  • no more food available
  • given toy
  • wake up
  • tired
  • bored

The cat has an internal value-context containing the belly filling level.

There are several state-transitions with guards, e.g. [belly empty] or [belly full] as well as timed transitions (see the image below).

Cat visualization

Enter the following code snippet into a smxstate node or load the example flow from here into node-red to get the example started:

// Import shorthands from xstate object
const { assign } = xstate; 

// First define names guards, actions, ...

/**
 * Guards
 */
const bellyFull = (context, event) => {
  return context.belly >= 100;
};

const bellyEmpty = (context, event) => {
    return context.belly <= 0;
}

const bellyNotEmpty = (context, event) => {
    return context.belly > 0;
}

/**
 * Actions
 */
const updateBelly = assign({
    belly: (context, ev) => Math.max(
        Math.min(
            context.belly+ev.value,100
        ), 0)
});

/**
 * Activities
 */
const meow = () => {
  const interval = setInterval(() => node.send({payload: "MEOW!"}), 2000);
  return () => clearInterval(interval);
};

/**
 * Services
 * see https://xstate.js.org/docs/guides/communication.html#invoking-callbacks
 */
const eat = (ctx, ev) => (cb) => {
    const id = setInterval(() => cb({
      type: 'eaten',
      value: 5
    }),500);
    return () => clearInterval(id);
};

const digest = (ctx, ev) => (cb) => {
    const id = setInterval(() => cb({
      type: 'digested',
      value: -5
    }),500);
    return () => clearInterval(id);
}

/***************************
 * Main machine definition * 
 ***************************/
 return {
  machine: {
    context: {
        belly: 0    // Belly state, 100 means full, 0 means empty
    },
    initial: 'sleep',
    states: {
      sleep: {
          on: {
              'wake up': 'meow',
          }
      },
      meow: {
          invoke: { src: 'digest' },
          on: {
              'given toy': { target: 'play', cond: 'belly not empty' },
              'given food': 'eat',
              'digested': { actions: 'updateBelly' }
          },
          activities: ['meow']
      },
      play: {
          invoke: { src: 'digest' },
          on: {
              tired: 'sleep',
              bored: 'sleep',
              'digested': { actions: 'updateBelly' },
              '': { target: 'meow', cond: 'belly empty' }
          },
          after: {
              5000: 'sleep',
          }
      },
      eat: {
          invoke: { src: 'eat' },
          on: {
              '': { target: 'sleep', cond: 'belly full' },
              'no more food': { target: 'meow' },
              'eaten': { actions: 'updateBelly' }
          }
      }
    }
  },
  // Configuration containing guards, actions, activities, ...
  // see above
  config: {
      guards: { "belly full": bellyFull, "belly not empty": bellyNotEmpty, "belly empty": bellyEmpty },
      activities: { meow },
      actions: { updateBelly },
      services: { eat, digest }
  }
}

This will give you a state machine to play with. It incorporates actions, delayed events, services, guards and internal data context. Observe the state-machine's state in the visualization panel while you inject events.

Development

  • Install dependencies using npm install
  • Trigger the build tool-chain using npm run devbuild to create a development build version that is easy to debug. If you use Visual Studio Code for development you can use the provided launch.json to run a node-red environment where you can quickly test the node. To use it first create a dir called ./tmp in this packages root dir and then change to it. Then run npm install .. to create a link to your working copy of the package for the node-red environment.
  • Running npm run build will create deployable production output in the ./dist directory.
  • To run tests, execute npx mocha ./tests. To add tests, create a files <description>_spec.js with a mocha test specification.
  • Using changesets: Before committing a change that should be visible on the changelog run npx changeset and input the corresponding information into the wizard. Then add the created file in the .changeset folder to the commit.
  • Publishing: If not done yet, login to npm using npm login. Then run npx changeset version to create a new version number, update package.json and the CHANGELOG.md files. Commit these changes with message Release VX.Y.Z. Then run npx changeset publish to publish to npm. Now run git push --follow-tags to push the version tag created by changeset.
  • Updating dependencies: run npm outdated to get a list of upgradeable packages. Then run npm update to update accordingly.

Acknowledgements

Caveats

  • If you use multiple nested state-machines using services, only the returned machine object will get rendered in the state-machines panel and the nested machines won't be drawn.
  • It is not possible to update the context from within an activity. Activities are per definition only fire-and-forget effects without communication back to the machine (see the post here). Instead you have to use services. See the sm-cat example's eat and digest services. In xstate V5 activities will be replaced by services completely.
  • ๐Ÿšจ Beware of copy-pasting flows that incorporate smxstate nodes from the internet. Although the javascript code provided within a node is evaluated in a node.js vm environment it is easy to paste malicious code. See this blog post for more information. To make sure nothing malicious is pasted, take a look at the javascript code within the node before deploying it in node-red.

node-red-contrib-xstate-machine's People

Contributors

dependabot-preview[bot] avatar hlovdal avatar sonntam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

node-red-contrib-xstate-machine's Issues

xstate node irrecoverably crashes node red if exception in node occurs

had an issue where an action was misspelled/missing which resulted in the node throwing an exception and permanently crashing Node Red ie. Node Red could no longer be restarted since it crashes again on the same issue

Had to manually edit the flows.json file and remove the xstate entry.

I don't think a misconfigured node should be able to crash Node Red that hard...

Node-Red running as a service on ubuntu 22.04:

nov. 29 17:36:54 cyclops Node-RED[2576060]: 29 Nov 17:36:54 - [info] Node-RED version: v3.1.0
nov. 29 17:36:54 cyclops Node-RED[2576060]: 29 Nov 17:36:54 - [info] Node.js  version: v20.9.0
nov. 29 17:36:54 cyclops Node-RED[2576060]: 29 Nov 17:36:54 - [info] Linux 5.15.0-88-generic x64 LE

How to reproduce:

Observ that this will most proably crash NodeRed so make backup of .node-red/projects/Home/flows.json

  1. create a new flow tab
  2. drop an smxstate node into it
  3. edit the properties of the node and rename one of the actions (incrementCounter or resetCounter)
  4. save
  5. deploy
  6. observ that node red is now dead and can not be restarted

Log output from node red:

nov. 29 17:36:41 cyclops Node-RED[2575948]: 29 Nov 17:36:41 - [info] Started flows
nov. 29 17:36:41 cyclops Node-RED[2575948]: 29 Nov 17:36:41 - [red] Uncaught Exception:
nov. 29 17:36:41 cyclops Node-RED[2575948]: 29 Nov 17:36:41 - [error] ReferenceError: incrementCounter is not defined
nov. 29 17:36:41 cyclops Node-RED[2575948]:     at SMXSTATE node:def654576503dc8a:126:16
nov. 29 17:36:41 cyclops Node-RED[2575948]:     at SMXSTATE node:def654576503dc8a:135:3
nov. 29 17:36:41 cyclops Node-RED[2575948]:     at Script.runInContext (node:vm:134:12)
nov. 29 17:36:41 cyclops Node-RED[2575948]:     at new StateMachineNode (/home/node-red/.node-red/node_modules/node-red-contrib-xstate-machine/dist/smxstate-node.js:435:34)
nov. 29 17:36:41 cyclops Node-RED[2575948]:     at Object.createNode (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/flows/util.js:157:27)
nov. 29 17:36:41 cyclops Node-RED[2575948]:     at Flow.start (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/flows/Flow.js:260:54)
nov. 29 17:36:41 cyclops Node-RED[2575948]:     at async Object.start [as startFlows] (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/flows/index.js:398:17)
nov. 29 17:36:41 cyclops systemd[1]: nodered.service: Main process exited, code=exited, status=1/FAILURE
nov. 29 17:36:41 cyclops systemd[1]: nodered.service: Failed with result 'exit-code'.

Import from stately.al

Hi, is it possible to import from a graphics editor stately.ai? If yes, how? I'm far from programming and couldn't figure it out.

Sorry for bad english

Warn on malformed objects

It can happen that a misplaced config section will produce misleading output. For example, actions may seem to work, but activities will produce No implementation found warnings.

For obviously missing sections, some sort of warning should be possible.

Can't run smxstate

Hi, I've just installed the node fromNR palette manager, and it shows errors after the installation:

27 Aug 20:16:11 - [info] Installed module: node-red-contrib-xstate-machine
27 Aug 20:16:11 - [info] Added node types:
27 Aug 20:16:11 - [info]  - node-red-contrib-xstate-machine:smxstate : SyntaxError: Unexpected token {

When I add a node to the flow and restart flows it stops with notification below:

27 Aug 20:16:26 - [info] Waiting for missing types to be registered:
27 Aug 20:16:26 - [info]  - smxstate

Any suggestions?
Maybe nodejs v 8.11.3 is to old?

A way to parse the xstate JSON thru inject node dynamically

Hello,

I am trying to create a dynamic flow that calls an xstate machine to load a particular machine JSON based on the scenario. Right now, I have to create more than 20 xstate nodes and I need to manage those nodes statically. Is there a way that we can parse the machine JSON dynamically using node or any workaround in order to achieve that functionality? I would really appreciate your help in achieving that

Thanks

Error when rendering state machine using smcat

I'm receiving a strange syntax error when trying to render my state machine using smcat. Only the rendering is affected, and the machine node works perfectly otherwise. Since the error message doesn't seem to relate in any meaningful way to my machine definition code, my guess is that the error has something to do with the way that the code is converted into the right format for smcat, hence why I am posting the issue here rather than smcat's repo. Here is my code:

// Import shorthands from xstate object
const { assign } = xstate;

return {
  machine: {
    context: {
      door: "open",
    },
    initial: "Unoccupied",
    states: {
      Unoccupied: {
        on: {
          buzz: [
            {
              target: "Occupied",
              cond: "door_is_closed",
            },
            {
              target: "Tentatively Occupied",
            },
          ],
          door_opened: {
            actions: {
              type: "mark_door_open",
              params: {}
            },
            internal: true,
          },
          door_closed: {
            actions: {
              type: "mark_door_closed",
              params: {}
            },
            internal: true,
          }
        }
      },
      Occupied: {
        on: {
          door_opened: {
            target: "Tentatively Occupied",
            actions: {
              type: "mark_door_open",
              params: {}
            }
          }
        }
      },
      "Tentatively Occupied": {
        after: {
          "60000": [
            {
              target: "Unoccupied",
              actions: []
            },
            {
              internal: false,
            }
          ],
        },
        on: {
          buzz: [
            {
              target: "Occupied",
              cond: "door_is_closed",
            },
            {
              internal: true,
            }
          ],
          door_closed: {
            actions: {
              type: "mark_door_closed",
              params: {}
            },
            internal: true,
          },
          door_opened: {
            actions: {
              type: "mark_door_open",
              params: {}
            },
            internal: true,
          }
        }
      }
    },
    predictableActionArguments: true,
    preserveActionOrder: true
  },
  config: {
    actions: {
      mark_door_open: assign({door: (context, event) => 'open'}),
      mark_door_closed: assign({door: (context, event) => 'closed'}),
    },
    services: { },
    guards: { door_is_closed: (context, event) => context.door == 'closed' },
    delays: { }
  }
};

And here is the error message from the console log:

Rendering of state machine failed: Render process returned error code 1: file:///data/node_modules/state-machine-cat/src/parse/smcat/smcat-parser.mjs:14
  var self = Error.call(this, message);
                   ^

peg$SyntaxError: Expected comment, end of input, line end, statemachine, or whitespace but "e" found.
    at new peg$SyntaxError (file:///data/node_modules/state-machine-cat/src/parse/smcat/smcat-parser.mjs:14:20)
    at peg$buildStructuredError (file:///data/node_modules/state-machine-cat/src/parse/smcat/smcat-parser.mjs:556:12)
    at peg$parse (file:///data/node_modules/state-machine-cat/src/parse/smcat/smcat-parser.mjs:2686:11)
    at Object.getAST (file:///data/node_modules/state-machine-cat/src/parse/index.mjs:22:22)
    at Object.render (file:///data/node_modules/state-machine-cat/src/index-node.mjs:58:24)
    at file:///data/node_modules/state-machine-cat/src/cli/actions.mjs:37:29
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  expected:...

Any help in solving this issue would be greatly appreciated!

Help with using events for guards

Hello - Just getting into trying out this module. I am stuck on trying to get a guard condition to work. The below setup doesn't work. Could use some help to tweak this. thx.

In the return state, I have the condition setup like this:

state_one: {target: 'system_off', cond: "myEvent"}

And

config: {
        guards: {myEvent:  (_, event) => event.payload = "incoming_payload_topic"}
 }

edit; Does xstate support the cond: setup like above?

persisting xstate

Hi there,

I am using "node-red-contrib-xstate-machine" plugin for a while now. It is very enjoyable, thanks for your effort.

My flow will be used on our production line and the state of the stations on the line is managed by the plugin.

Now I have a need to to presist the xstate nodes, as the flow might be restarted from time to time.

Is there a way to persist the state of the node like mentioned in https://xstate.js.org/docs/guides/states.html#persisting-state ?

Thanks in advance,
Ferhat

Display state node context

How can I display the state node's context?

There is a Node-RED Context Data panel that (should) display Node, Flow, and Global context.

The Node context does not display the xstate Node context.

Access to rendering options

Based on what I see here, there are some rendering controls that are not exposed. It would be very useful to control the render direction and the fi to width option. For the latter, It might be sufficient if fit to width was just always on, as in my case, a long skinny chart is difficult to see what's happening here.

Activities Constantly Running Outside of Correct States

Hello,

I have an issue where activities are running no matter what state the machine is in.
activitiesissue
activitiesissue2

Even though the 'elapse_time' activity is only in the 'running' state, it runs in every state. Any help would be greatly appreciated.

Docker install: "Flows stopped due to missing node types. smxstate"

I am running Node-RED (0.20.7) on my Raspberry Pi in Docker (19.03.8). (The reason for the specific Node-RED version is an incompatibility with IKEA Tradfri of builds above 1.0).

When I want to install node-red-contrib-xstate-machine using the palette, the following happens:

  • Node-RED UI says the node is installed.
  • The node appears on the palette.
  • I can add it to a new flow.
  • The default xstate machine script appears on the Properties page.

However, when I want to deploy the changes, I get the following error message:

Flows stopped due to missing node types.
smxstate

Checking Node-RED log, the dependencies failed to install:

12 Apr 23:59:56 - [info] Waiting for missing types to be registered:,
12 Apr 23:59:56 - [info] - smxstate

The tail of "npm install node-red-contrib-xstate-machine" in the Docker container console output is:

npm info lifecycle [email protected]postinstall: [email protected]
npm info lifecycle [email protected]
postinstall: [email protected]
npm info lifecycle @types/[email protected]postinstall: @types/[email protected]
npm info lifecycle undefined
preshrinkwrap: undefined
npm info lifecycle undefinedshrinkwrap: undefined
npm info lifecycle undefined
postshrinkwrap: undefined
npm WARN [email protected] requires a peer of canvas@^2.5.0 but none was installed.
npm WARN [email protected] requires a peer of bufferutil@^4.0.1 but none was installed.
npm WARN [email protected] requires a peer of utf-8-validate@^5.0.2 but none was installed.

Any idea why these dependencies do not install?

Thx

Can't find xstate

Hello -
I installed xstate from the node-red palette manager. ( v.1.2.2). Running node-red in an ubuntu 21.10 vm, node version 14.18.3, node-red version 2.1.6.

Everything shows up correctly, except in the editor, I get the error below. Any ideas?

xstate

multi instance for every message

hello,
the actual implmentation can support many instances of the same state machine.
my idea is of passing the state and the data for every message and in this mode i can expose the state machine via an api rest and each session user have is state and data but the same implmentation of the state machine

Entry action not firing when deploying flows.

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

Warning: No implementation found for activity

I have a flow that was started from the default flow in the smxstate node. I added more states and actions, an renamed the dostuff activity, but now I can't get the activity implementation to be found. All I see in the console log is the Warning that is the subject of this issue.

Is there some way I can get more verbose logging? Or some way to trace into what exactly is (not) happening in my machine config?

If the implementation is not found, how is it that the flow able to be deployed?

Here's the complete payload from the top output when transitioning to the state with the activity:

{"state":{"enabled":{"ticket":{"call":"running"}}},"changed":true,"done":false,"activities":{"check":{"type":"xstate.start","activity":{"type":"check"}}},"actions":[{"type":"xstate.start","activity":{"type":"check"}},{"type":"xstate.send","event":{"type":"xstate.after(30000)#c5791bba.c55e88.enabled.ticket.call.running"},"delay":30000,"id":"xstate.after(30000)#c5791bba.c55e88.enabled.ticket.call.running","_event":{"name":"xstate.after(30000)#c5791bba.c55e88.enabled.ticket.call.running","data":{"type":"xstate.after(30000)#c5791bba.c55e88.enabled.ticket.call.running"},"$$type":"scxml","type":"external"}}],"event":{"type":"RUNNING","payload":1628623713161},"context":{"startLevel":0,"stopLevel":0,"runTime":0}}

Here's the flow:

const { assign } = xstate;


/**
 * Guards
 */
const maxRunTimeElapsed = (context, event) => {
  return context.runTime >= maxRunTime;
};

/**
 * Actions
 */
const ticketSend = (context, event) => {
    node.send({ payload: "Ticket Complete" });
    console.log('ticket complete time:', Date.now());
};

const incrementCounter = assign({
  counter: (context, event) => context.counter + 1
});

const pumpStop = (context, event) => {
    node.send({ payload: "Pump Stop" });
    console.log('pump stop time:', Date.now());
};

const pumpStart = (context, event) => {
    node.send({ payload: "Pump Start" });
    console.log('pump start time:', Date.now());
};

const ticketStateSet = (context, event) => {
    node.send({ payload: "Set Ticket State" });
    console.log('set ticket state time:', Date.now());
};

/**
 * Activities
 */
 //See https://xstate.js.org/docs/guides/activities.html
const check = () => {
    const interval = setInterval(() => {
        node.send({ payload: 'Check Level' });
        }, 2000);
    return () => clearInterval(interval);
};

const doStuff = () => {
  // See https://xstate.js.org/docs/guides/activities.html
  const interval = setInterval(() => {
    node.send({ payload: 'BEEP' });
  }, 1000);
  return () => clearInterval(interval);
};

return {
  machine: {

    // Local context for entire machine
    context: {
      startLevel: 0,
      stopLevel: 0,
      dispenseQty: global.get('dispenseQty'),
      runTime: 0,
      maxRunTime: global.get('startLevel')
    },

    // Initial state
    initial: 'off',

    // State definitions
    states: {
      off: {
        /* Platform secret switch is open */
        on: {
          ENABLE: 'enabled'
        }
      },
      enabled: {
        /* Platform secret switch is closed */
        //type: 'compound',
        initial: 'idle',
        on: {
            DISABLE: 'off'
        },
        states: {
          idle: {
              on: {
                TICKET: 'ticket'  
              }
          },
          ticket: {
            //type: 'compound',
            initial: 'idle',
            onDone: 'idle',
            states: {
              idle: {
                entry: ticketStateSet,
                on: {
                  CALL: 'call',
                  COMPLETE: 'complete'
                }
              },
              call: {
                //type: 'compound',
                initial: 'starting',
                entry: ticketStateSet,
                onDone: 'complete',
                states: {
                  starting: {
                    entry: pumpStart,
                    on: {
                      RUNNING: 'running'
                    }
                  },
                  running: {
                    on: {
                      STOP: 'stopping',
                      STOPPED: 'stopped'
                    },
                    after: {
                      30000: { target: 'stopping' }
                    },
                    activities: check
                  },
                  stopping: {
                    entry: pumpStop,
                    on: {
                      STOPPED: 'stopped'
                    }
                  },
                  stopped: {
                    type: 'final'
                  }
                }
              },
              complete: {
                type: 'final',
                entry: [ticketSend, ticketStateSet]

              }
            }
          }
        }
      }
    },
    config: {
      activities: {check},
      actions: {
        ticketSend,
        pumpStart,
        pumpStop,
        ticketStateSet
      },
      guards: {
        maxRunTimeElapsed
      }
      
      //delays: {
      //  /* ... */
      //},
      //services: {
        /* ... */
      //}
    }
  }
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.