State Machine - How to Compare Numbers?

Hello, I´m trying to learn and do some tests with the State Machine Node, but I´m having problems to use number comparisons. The = works fine, but the > #number seems to require a certain syntax that I´m not sure.

Below the very simple flow I´m testing:

And the configuration inside:

The >400 doesn´t work and it seems the Node waits for a string ">400" but not an actual number that is over 400.

The trigger State Node is configured to Number.
What am I doing wrong?

It looks like you are using node-red-contrib-state-machine. That node hasn’t been updated in over 5 years. You could raise an issue with the author:

You may want to read the issue ‘State machine makes use of a deprecated function’ or ask on the HomeAssistant forum for what people are using as a replacement.

Hi Zenofmud,

I´m actually using this one:

https://github.com/hufftheweevil/node-red-contrib-persistent-fsm

It seems to be quite new. But you gave me a good idea to ask directly there.
Thanks a lot.

It helps to identify, using the full name, when referring to a node. It also saves the time of anyone trying to help you.

Noted for the next time, which will be my second post.

1 Like

You can also build a FSM using some Javascript in a function node.
Here's copy of a very simple FSM that mimics the seqence of a UK traffic light.

Note:
You will need to change the conditions for the state-transitions to suit your application.

[{"id":"730e90628b9f186a","type":"tab","label":"Simple FSM","disabled":false,"info":"","env":[]},{"id":"760b4fc6.26e1","type":"function","z":"730e90628b9f186a","name":"Decode RED light","func":"var fsm_state = flow.get(\"state_counter\");\n\nif (fsm_state === \"S0\" || fsm_state == \"S1\")\n   {msg.payload = 1;\n    node.status({fill:\"red\",shape:\"dot\",text:\"Red ON\"});\n   }\n   \nelse\n   {msg.payload = 0;\n    node.status({});\n   }\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":240,"wires":[[]]},{"id":"c24343bf.b80df8","type":"function","z":"730e90628b9f186a","name":"Decode YELLOW light","func":"var fsm_state = flow.get(\"state_counter\");\n\nif (fsm_state == \"S1\" || fsm_state == \"S3\")\n   {msg.payload = 1;\n   node.status({fill:\"yellow\",shape:\"dot\",text:\"Yellow ON\"});\n   }\nelse\n   {msg.payload = 0;\n   node.status({});}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":300,"wires":[[]]},{"id":"6b986e0f.b813b8","type":"function","z":"730e90628b9f186a","name":"Decode GREEN light","func":"var fsm_state = flow.get(\"state_counter\");\n\nif (fsm_state == \"S2\")\n   {msg.payload = 1;\n   node.status({fill:\"green\",shape:\"dot\",text:\"Green ON\"});\n   }\nelse\n   {msg.payload = 0;\n   node.status({});\n   }\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":360,"wires":[[]]},{"id":"801d6dfd.1244","type":"inject","z":"730e90628b9f186a","name":"Trigger every 3 seconds","props":[{"p":"payload"}],"repeat":"3","crontab":"","once":true,"onceDelay":"","topic":"","payload":"1","payloadType":"num","x":190,"y":300,"wires":[["d748e7bb.cdeb78"]]},{"id":"d748e7bb.cdeb78","type":"function","z":"730e90628b9f186a","name":"Index the State Counter","func":"// Here is the classical way of coding a state machine.\n// It uses a 'case construct' to check the current state and then set the next state.\n\nvar fsm_state = flow.get(\"state_counter\") || \"S0\";\n\nswitch (fsm_state)\n    {\n        case \"S0\":\n            fsm_state = \"S1\";\n            break;\n            \n        case \"S1\":\n            fsm_state = \"S2\";\n            break;\n        \n        case \"S2\":\n            fsm_state = \"S3\";\n            break;\n            \n        case \"S3\":\n            fsm_state = \"S0\";\n            break;\n    }\n    \nflow.set(\"state_counter\", fsm_state);\nmsg.payload = fsm_state;\nnode.status({text:\"State counter = \" + fsm_state});\n\nreturn msg;","outputs":"1","noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":300,"wires":[["760b4fc6.26e1","c24343bf.b80df8","6b986e0f.b813b8"]]},{"id":"4f023541f75009b8","type":"comment","z":"730e90628b9f186a","name":"Simple FSM to mimic the UK traffic light sequence","info":"","x":370,"y":240,"wires":[]}]
1 Like

Just had a few spare minutes, so I quickly built this simple flow. Can you check if it works for you?

FSM_monday_2

[{"id":"730e90628b9f186a","type":"tab","label":"Simple FSM","disabled":false,"info":"","env":[]},{"id":"801d6dfd.1244","type":"inject","z":"730e90628b9f186a","name":"0","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"0","payloadType":"num","x":150,"y":300,"wires":[["d748e7bb.cdeb78"]]},{"id":"d748e7bb.cdeb78","type":"function","z":"730e90628b9f186a","name":"FSM","func":"// Here is the classical way of coding a state machine.\n// It uses a 'case construct' to check the current state and then set the next state.\n\nif (msg.topic == \"reset\") {\n    flow.set(\"state_register\", \"S1\");\n}\n\n\nvar fsm_state = flow.get(\"state_register\") || \"S1\";\n\nswitch (fsm_state)\n    {\n        case \"S1\":\n        if (msg.payload == 100) {\n            fsm_state = \"S2\"; \n            }\n        break;\n            \n        case \"S2\":\n        if (msg.payload == 200) {\n            fsm_state = \"S3\";\n            }\n        break;\n        \n        case \"S3\":\n        if (msg.payload == 300) {\n            fsm_state = \"S4\";\n            }\n        break;\n            \n        case \"S4\":\n        if (msg.payload > 400) {\n            fsm_state = \"S1\";\n        }\n        break;\n    }\n    \nflow.set(\"state_register\", fsm_state);\nmsg.payload = fsm_state;\nnode.status({text:\"State register = \" + fsm_state});\n\nreturn msg;","outputs":"1","noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":300,"wires":[["eab829b3b68ccf9f"]]},{"id":"eab829b3b68ccf9f","type":"debug","z":"730e90628b9f186a","name":"debug 30","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":480,"y":300,"wires":[]},{"id":"32e0a48416d4db02","type":"inject","z":"730e90628b9f186a","name":"Reset to S1","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"reset","x":170,"y":220,"wires":[["d748e7bb.cdeb78"]]},{"id":"82cc0d69adc947ee","type":"inject","z":"730e90628b9f186a","name":"100","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"100","payloadType":"num","x":150,"y":360,"wires":[["d748e7bb.cdeb78"]]},{"id":"e260485be0374b83","type":"inject","z":"730e90628b9f186a","name":"200","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"200","payloadType":"num","x":150,"y":420,"wires":[["d748e7bb.cdeb78"]]},{"id":"061eba96ddac281b","type":"inject","z":"730e90628b9f186a","name":"300","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"300","payloadType":"num","x":150,"y":480,"wires":[["d748e7bb.cdeb78"]]},{"id":"3876685887570362","type":"inject","z":"730e90628b9f186a","name":"401","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"401","payloadType":"num","x":150,"y":540,"wires":[["d748e7bb.cdeb78"]]}]

Your code looks simple and effective.
I added to a new function Node but I could not get it to the initial state. Looking to the code I was supposed to set "reset" to the msg.topic. I did that but no result is seen in my log. I tried also with the numbers directly from my slider, but it seems it needs to get to the initial status first (means "reset" in the msg.topic).

Here it is the way I configured:
image

But no result goes out to my debug node 9.

Another question, is there a way to format your code with all Java formatting and identation? It makes much easier to debug and re-work.

Thanks in advance.

Did you try just my simple flow to see if it works on your set-up?

It doesn't look as if the FSM is getting reset to the initial state "S1" in YOUR setup.
You need to fix that issue.

Here's the Java-Script code (not Java) formatted as requested.

// Here is the classical way of coding a state machine.
// It uses a 'case construct' to check the current state and then set the next state.

if (msg.topic == "reset") {
    flow.set("state_register", "S1");
}


var fsm_state = flow.get("state_register") || "S1";

switch (fsm_state) {
    case "S1":
        if (msg.payload == 100) {
            fsm_state = "S2";
        }
        break;

    case "S2":
        if (msg.payload == 200) {
            fsm_state = "S3";
        }
        break;

    case "S3":
        if (msg.payload == 300) {
            fsm_state = "S4";
        }
        break;

    case "S4":
        if (msg.payload > 400) {
            fsm_state = "S1";
        }
        break;
}

flow.set("state_register", fsm_state);
msg.payload = fsm_state;
node.status({ text: "State register = " + fsm_state });

return msg;

Dear Dynamicdave,

it works perfectly! Thanks a lot.
Allow me to make a stupid question. Why most like you send the code in the previous format? It is much more difficult to read and adjust, at least for me. Is it some kind of compilation to support the developer? Or are you using some kind of formatting conversion?

Thanks a lot for your support.

The former is actually a complete export of the code as shown in his picture - ie a small test flow... you can use the button one in from top right of that code window
image
to copy the code to your clipboard - then in Node-RED editor you can use Ctrl-I (or Menu - Import) and paste in that code to have it load into your editor... hopefully as a working example :slight_smile:

2 Likes

As @dceejay explained, this is the most common way (format) to share Node-RED flows.
Just follow @dceejay instructions and try it out - it is quite easy once you are familiar with it.

PS: Glad to hear the flow works for you.

1 Like

Thanks for sharing the code in an easy way,
It’s also easier for people like me that are learning trough the forum without having the time to test all nodes configuration in node ref.
Just one question, I saw often in function example a flow.get at the beginning and flow.set at the end but I learned recently that the link for context variable is a reference, so I would say that the flow.set at the end is not useful . Could you confirm it is working the same way when removing it?

Pierre RUWET

The answer is sometimes… if you are only using memory based context then yes it will probably work by reference. However if you use any other form of context storage eg file or database then you must explicitly write back to it, so in general it is safer to always get into the good habit and call the write. It is also easier to read later when you need to debug in 6 months time.

Regarding the general subject of state machine, I have been using the node red contrib dsm with success. I found it interesting because there are a lot of examples in the documentation.
I don’t understand why a node should automatically be considered bad because it had no upgrade for 4 years..
it just mean for me that the code was very good at the beginning and that no bug where discovered that need an update.
This is also because it is using basic Java code without external connection, so less need to update.

Pierre RUWET

1 Like

In addition to @dceejay's comment, if the object in context is a simple value rather than an object then I believe you always need flow.set().

1 Like

A better guide is to look at the issues on its github page and whether they have been answered or not. A node that is not supported may stop working at some point if it becomes incompatible with the latest nodejs.

2 Likes

Thanks for the advice, I will check if I can easily replace my dsm node by dsm or by a function node.
Contrib node are interesting for people that are not a JS specialist. This is my case. I never used JS before starting with nodered two years ago.

I found limitations in node-red-contrib-state-machine when your states are numbers and you need a state > or < a certain number. If you add this in your parameter ‘>10’ the block will interpret that this a string and not a number comparison.
The JS code provided by dynamicdave was more flexible and straightforward.
I also think that many, except electric engineers in general, do no understand the concept of FSM. That might be why the node is not so popular.

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