Source Code Line Wrap

Sorry for asking JS related matters again. Up on now, I use the NodeRed editor. The features are not thrilling, but it is doing its job for simple node related code snippets. Mainly, my concern is to keep all source lines short an simple. Sometimes there are non trivial lines. Longer JSONS might be distributed to several lines as the members are inside curly braces {}. Strings are inside "hyphens" and might line wrap using the C-style backslash. For other source code I failed.

msg.out =   (Kp * error) + (Ki * msg.errsum) + Kd * (error - msg.errold);          // This PID Regulator is a long line although we use only short symbol names and almost not comment behind.

I tried to wrap the formula in three lines, one for each term. Although the formula code is not interrupted by any comments, the JIT compiler seems not executing everything collected between cr and limit colon; There are also no syntax error hints while editing or execution. It simply does not work.

msg.out =   (Kp * error) + 
            (Ki * msg.errsum) + 
             Kd * (error - msg.errold);          // PID Regulator

How is the trick to wrap lines for JS?

The trick is to make valid syntax :wink:

BTW, what you have is valid syntax (assuming Kp, error, Ki and Kd are defined somewhere):

No errors:

Errors when syntax is incorrect

In what way? Have you verified input msg contains .errsum and .errold?

Try adding some node.warns in your code and they will give you interal runtime values:

node.warn({
    _info: "Calculating out from these values",
    Kp,
    Ki,
    Kd,
    error,
    "msg.errsum": msg.errsum,
    "msg.errold": msg.errold,
});

msg.out = (Kp * error) +
          (Ki * msg.errsum) +
           Kd * (error - msg.errold);

node.warn(`New value for msg.out = ${msg.out}`)

Perhaps

msg.out  = (Kp * error) 
msg.out += (Ki * msg.errsum)
msg.out += Kd * (error - msg.errold)

Ok, there seems nothing for line wrap like in C. Node works like expected, with formula inside one line but does not with same formula inside three lines. Round braces are optional, only to recognize the terms for humans. Error seems predefined anywhere accidently but I use error. Here is complete flow within the const Kp, Ki, Kd.

[{"id":"eb2930e347521e17","type":"function","z":"a9b943e599c9cad0","name":"PID","func":"//msg.out = Manipulated Value is grid setpoint, Stellgroesse ist die Netzeinspeisung\n//flow.sv = Setpoint Value, Sollwert ist gewuenschter SOC Batterie Ladezustand\n//flow.pv = Process Value, Ist wert ist der aktuelle SOC Batterie Ladezustand\n\nconst Kp = 300;                                     // proportional parameter\nconst Ki = 100;                                     // integraion parameter\nconst Kd = 1;                                       // differential parameter\n\nlet error = (flow.get(\"sv\") - flow.get(\"pv\"))*-1;   // difference between setpoint value and process value\nmsg.errsum += error;                                // integral difference\n\nflow.set (\"ERRSUM\" , msg.errsum);\n\nmsg.out =   (Kp * error) + (Ki * msg.errsum) + Kd * (error - msg.errold); // PID Regulator\nmsg.errold = error;                             // save Error for next cycle\n\nif (msg.out > 33000) msg.out = 33000;           // top saturation\nif (msg.out < 300) msg.out = 300;               // bottom saturation\nmsg.out = Math.round(msg.out *-1);              // small SOC requires high negative grid power\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":850,"y":260,"wires":[["f42587e7a286daf9","a7527499cb68a56e"]]},{"id":"a7527499cb68a56e","type":"delay","z":"a9b943e599c9cad0","name":"PT1","pauseType":"delay","timeout":"250","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":710,"y":260,"wires":[["eb2930e347521e17"]]},{"id":"b298fdb9d94a8061","type":"inject","z":"a9b943e599c9cad0","name":"Reset","props":[{"p":"out","v":"3","vt":"num"},{"p":"errsum","v":"4","vt":"num"},{"p":"errold","v":"5","vt":"num"}],"repeat":"","crontab":"","once":true,"onceDelay":"0","topic":"","x":710,"y":200,"wires":[["eb2930e347521e17"]]},{"id":"f42587e7a286daf9","type":"debug","z":"a9b943e599c9cad0","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"out","targetType":"msg","statusVal":"","statusType":"auto","x":1000,"y":260,"wires":[]}]

Please post a failing flow that we can test. The one you posted expects flow context to be setup and does not have any wrapped lines.

Here is the version with the wrapped text. It does something else than the non wrapped version.
Obviosly JS does not allow this. There is no sense to run the nodes without the flow global sv and pv signals provided by hardware.

[{"id":"7140d8687e7864a9","type":"function","z":"a9b943e599c9cad0","name":"PID","func":"//msg.out = Manipulated Value is grid setpoint, Stellgroesse ist die Netzeinspeisung\n//flow.sv = Setpoint Value, Sollwert ist gewuenschter SOC Batterie Ladezustand\n//flow.pv = Process Value, Ist wert ist der aktuelle SOC Batterie Ladezustand\n\nconst Kp = 300;                                     // proportional parameter\nconst Ki = 100;                                     // integraion parameter\nconst Kd = 1;                                       // differential parameter\n\nlet error = (flow.get(\"sv\") - flow.get(\"pv\"))*-1;   // difference between setpoint value and process value\nmsg.errsum += error;                                // integral difference\n\nflow.set (\"ERRSUM\" , msg.errsum);\n\nmsg.out =   (Kp * error) + \n            (Ki * msg.errsum) +\n             Kd * (error - msg.errold); // PID Regulator\n\nmsg.errold = error;                             // save Error for next cycle\n\nif (msg.out > 33000) msg.out = 33000;           // top saturation\nif (msg.out < 300) msg.out = 300;               // bottom saturation\nmsg.out = Math.round(msg.out *-1);              // small SOC requires high negative grid power\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":930,"y":420,"wires":[["293e3cf665d9a44d","649946cc3381c822"]]},{"id":"649946cc3381c822","type":"delay","z":"a9b943e599c9cad0","name":"PT1","pauseType":"delay","timeout":"250","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":790,"y":420,"wires":[["7140d8687e7864a9"]]},{"id":"95be46f8dd212fd0","type":"inject","z":"a9b943e599c9cad0","name":"Reset","props":[{"p":"out","v":"3","vt":"num"},{"p":"errsum","v":"4","vt":"num"},{"p":"errold","v":"5","vt":"num"}],"repeat":"","crontab":"","once":true,"onceDelay":"0","topic":"","x":790,"y":360,"wires":[["7140d8687e7864a9"]]},{"id":"293e3cf665d9a44d","type":"debug","z":"a9b943e599c9cad0","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"out","targetType":"msg","statusVal":"","statusType":"auto","x":1080,"y":420,"wires":[]}]

The sense is that you must provide an example flow that shows that the line wrap is failing. Add a Change node that sets the flow variables to appropriate values so that we can test it, and tell us what you see in the debug node and what it should be. We don't know what are appropriate values.

Still trying to test this, I'm not getting valid output at the moment.

One thing to note is that "standard" JS formatting requires the +, -, =, etc at the START of a continuation line not at the end of the previous line. Don't think this actually impacts execution though, trying to test now.

Also, your examples appear to be auto-starting infinite loops which is not friendly to those of us trying to help.

That is odd, I have always put it at the end of the first line so that it is obvious that there is a continuation.

1 Like

Yes, me too, by instinct really, but eslint in full VS Code is telling me otherwise when loaded with standard js settings.

1 Like

We need the sv and pv example values please. It is generally best practice to to the flow.get's on a separate line with a fallback default using ??.

Sorry to bother you with everything around. This post started with only one line of code. I am going to make a test project with valid inputs and output visualisation to visualize what the PID node is doing. This will take a while as the code works in one line and I am proceeding with the project next few hours. After this, I am going to strip the PID node to insert in a working example what does not depend from hardware. Thanks.

I'm a bit confused now (not hard after a long day), but both examples seem to work - though I've disconnected the annoying loop.

All we need is an inject node, a Function node and a debug node showing the problem.

1 Like

In the meantime I did no demo of the PID node, but found the true problem what I observed several times without explanation.

  1. The PID node circles some messages to itself in the feedback loop.

  2. The setpoint value sv is supplied by a dropdown menu in the dashboard

  3. The difference of setpoint value sv and process value pv is the regulators error

let error = (flow.get("sv") - flow.get("pv"))*-1;

  1. This error is accumulated as the lowpass integral value and then feed back for next cycle

msg.errsum += error;

  1. Everything works fine except some times. Some of the values of the dropdown menu were numbers and some were accidently string format. The difference of a string in sv is NaN what goes into the msg.errsum to circle around in the feedback loop. This already stops the correct operation.

  2. After selecting another setpoint value what also might be a correct number format, correct operation does not come back as msg.errsum is still NaN plus a valid number what gives NaN again.

  3. After reset and new deploy everything seems working again. Of coarse the reason was not the line wrap what was the only thing I did in the meantime.

You might be interested in my pid node, node-red-contrib-pid, which several have found useful.

Many thanks for this hint. As PID is something very basic for control (as FSM also is) there are many different PID nodes found in the library. The decision is difficult on which is maintained or good enough for closer examination. For the moment, I try to reinvent all required wheels myself again as I am eager to learn more about NodeRed.

Mainly, I dont want to have too much dependencies for the project. On the other hand, your PID node seems already very advanced and displays error message in the status line if feed with NaN. Probably I could learn plenty of things on how to deal with NodeRed. Already read your hints in the blog on how to optimize the parameter values.

How is it possible to examine the underlying source code? After installation, I only see the html form for setup values.

As with all nodes you install the source will be available in .node-red/node_modules/node-red-contrib-pid, or (for nodes that have a github repo) it can be examined on its github page.

I first developed the algorithm for one of the first micro based process controllers in about 1981 and it has been ported to several languages and hardware platforms since then. The latest is javascript for node-red. You can be confident the algorithm is well tried and tested.

3 Likes

Shouldnt .node-red be in the home directory?

jv@JamesWebb:~$ find ~/.npm/_update*
/home/jv/.npm/_update-notifier-last-checked
jv@JamesWebb:~$ find ~/.node-red*
find: ‘/home/jv/.node-red*’: No such file or directory
jv@JamesWebb:~$ 

How did you install it?