How to start actions from a script

Revision as of 15:52, 24 May 2024 by Registered User (add the led example)

This page explains how to start some actions from an external script

1. Introduction

STM32CubeMonitor provides the environment to create flows to build dashboard or manage some action. Thanks to Finite State Machine provided in node-RED ecosystem, it is possible to build complex automatized process for your dashboards or to drive some action on STM32 devices. It is also possible to execute some "command line" action with the exec node. So most of the time, STM32CubeMonitor will manage your measurements automatically.

Some users need to drive the actions from an external script ( .bat, .sh, ...) or external tools. In this case, interacting with the graphical UI of STM32CubeMonitor is not efficient. This page will explain how it is possible to use the http node to allow a script or external application to trig actions in the STM32CubeMonitor flow.

2. The concept

As soon as STM32CubeMonitor is started, the flow are running. Even if the dashboard page is not open, flows are working in background. The solution is to add in the flow some http nodes to catch requests from user script and perform the actions requested. The command curl can be used easily in command line or script to send the request to STM32CubeMonitor. When the action is finished, the result is sent back in the http response.

Interaction between script and STM32CubeMonitor


3. The HTTP node

The HTTP node is used to send or receive HTTP request in Node-RED. For the action from script case, the "http in" and "http response" nodes are used.

Simple HTTP example

The "http in" node will receive a http request on url "demo" (http://localhost:1880/demo). The message is passed to a template node to prepare the answer, and then answer is sent back through "http response node".

The request can be sent from a command line with curl or from a web browser.

Test flow :

[{"id":"1fb8a74d55ae9749","type":"http in","z":"4eae8e47b3603ea6","name":"","url":"/demo","method":"get","upload":false,"swaggerDoc":"","x":300,"y":320,"wires":[["4e47e385987aad47"]]},{"id":"d92d3f76d1186408","type":"http response","z":"4eae8e47b3603ea6","name":"","statusCode":"","headers":{},"x":670,"y":320,"wires":[]},{"id":"4e47e385987aad47","type":"template","z":"4eae8e47b3603ea6","name":"Build answer ","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"The command is : {{payload.cmd}}\nThe parameter is : {{payload.param}}\n","output":"str","x":490,"y":320,"wires":[["d92d3f76d1186408"]]}]

To test the flow, go to "Menu > Import > Clipboard" and Copy/Paste the .json flow in the area, and then click on Import.

Then on a command line use : curl "http://localhost:1880/demo?cmd=test1&param=hello" or put "http://localhost:1880/demo?cmd=test1&param=hello" in a web browser. The result should be

>curl "http://localhost:1880/demo?cmd=test1&param=hello"
The command is : test1
The parameter is : hello

The element after the ? are parameters. In this example there is a parameter "cmd" and another parameter "param. The "template" node use it to build the answer. The name are free and the parameters can be used for any purpose.


4. The http actions implementation

The previous example was just illustrating the http nodes, but not really useful. Here is a more interesting flow, showing how to manage multiple commands, with immediate or delayed responses.

Flow used to managed actions from http requests
[{"id":"b6723204.12023","type":"http in","z":"60ec2a4b.5ad324","name":"","url":"/cli","method":"get","upload":false,"swaggerDoc":"","x":270,"y":320,"wires":[["f0aa7f90.176ca"]]},{"id":"b7bfe1ac.9ea71","type":"http response","z":"60ec2a4b.5ad324","name":"","statusCode":"","headers":{},"x":1070,"y":360,"wires":[]},{"id":"f0aa7f90.176ca","type":"switch","z":"60ec2a4b.5ad324","name":"","property":"payload.cmd","propertyType":"msg","rules":[{"t":"eq","v":"test1","vt":"str"},{"t":"eq","v":"test2","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":3,"x":410,"y":320,"wires":[["90147b04.5474d8"],["dd4df7c0.d02e78","d1ae2912.4a8b28"],["5ed52e43.7efaa"]]},{"id":"5ed52e43.7efaa","type":"template","z":"60ec2a4b.5ad324","name":"unknown command","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Invalid command","x":630,"y":500,"wires":[["b7bfe1ac.9ea71"]]},{"id":"568331f2.a1eae","type":"comment","z":"60ec2a4b.5ad324","name":"receive HTTP request","info":"curl --silent http://localhost:1880/cli?cmd=test1\ncurl --silent \"http://localhost:1880/cli?cmd=test1&param=hello\"","x":340,"y":260,"wires":[]},{"id":"e5a43097.a3995","type":"join","z":"60ec2a4b.5ad324","name":"join new payload","mode":"custom","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":850,"y":360,"wires":[["b7bfe1ac.9ea71"]]},{"id":"dd4df7c0.d02e78","type":"change","z":"60ec2a4b.5ad324","name":"remove payload","rules":[{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":340,"wires":[["e5a43097.a3995"]]},{"id":"4b1cf72.3c23a08","type":"comment","z":"60ec2a4b.5ad324","name":"command to send ","info":"1) command executed immediately\n curl \"http://localhost:1880/cli?cmd=test1&param=hello%20world\"\n\n2) command with delayed answer\n curl \"http://localhost:1880/cli?cmd=test2&param=test\"\n \n 3) Invalid command \n  curl \"http://localhost:1880/cli?cmd=test3&param=867\"\n\n","x":330,"y":420,"wires":[]},{"id":"333e6ce4.35b9c4","type":"inject","z":"60ec2a4b.5ad324","name":"Send the result","props":[{"p":"payload"},{"p":"complete","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"This is the result","payloadType":"str","x":640,"y":380,"wires":[["e5a43097.a3995"]]},{"id":"d1ae2912.4a8b28","type":"debug","z":"60ec2a4b.5ad324","name":"log received request","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":640,"y":300,"wires":[]},{"id":"90147b04.5474d8","type":"template","z":"60ec2a4b.5ad324","name":"Immediate answer ","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"The command is : {{payload.cmd}}\nThe parameter is : {{payload.param}}\n","output":"str","x":630,"y":180,"wires":[["b7bfe1ac.9ea71"]]},{"id":"c224d87c.d2e008","type":"comment","z":"60ec2a4b.5ad324","name":"sendHTTP resp","info":"","x":1040,"y":280,"wires":[]},{"id":"10e65355.bea00d","type":"comment","z":"60ec2a4b.5ad324","name":"Immediate processing and answer","info":"","x":640,"y":140,"wires":[]},{"id":"91c65281.dde4e","type":"comment","z":"60ec2a4b.5ad324","name":"Answer after processing","info":"The request is logged in the debug node.\nUse the Inject node to send the result.","x":640,"y":260,"wires":[]},{"id":"1c77828c.c367fd","type":"comment","z":"60ec2a4b.5ad324","name":"Invalid command","info":"This answer is used when command is not valid.\nHTTP status is OK (200) but could be forced to error code.","x":640,"y":460,"wires":[]}]

In this flow, afirst check is done on the cmd parameters. The "switch" node will send the message to the correct part of the flow :

  • The top part is similar to previous exemple : the answer is prepared and sent immediately to requester. It can be useful some information are already known in the flow and should be returned (note that flow is always running, and data could be memorized in flow "context")
  • The second part is for a command which requires some processing time. In this case, the request is added to the log and the user must click on the "inject"node to send the result. The http request in console is waiting until user click to send the answer. When user clicks, the answer is joined to the http request message, and sent back to the http response node. The "http response" node needs the original "req" part of the request to be able to answer. This is why there is a need to join the original message from "http in" without the payload and the new payload coming from the "inject" node.
  • The third part is for a wrong command. In this case, and error message is sent immediately to unblock the console.
>curl "http://localhost:1880/cli?cmd=test1&param=hello%20world"
The command is test1
The parameter is : hello world
>curl "http://localhost:1880/cli?cmd=test2&param=867"
This is the result
>curl "http://localhost:1880/cli?cmd=test3"
Invalid command


This example can be adapted to trig any actions on the flow. The http request can be sent by console, scripts or any software.


5. Example : Toggling Nucleo LED from a command line

This flow allow to set and read a register from the command line. It can be used to turn on/off LED GPIO. The address of ODR register is passed in the command line and the value to write.

Setting and Reading GPIO from command line example
[{"id":"8381c865.236028","type":"subflow","name":"Single value","info":"The 'single value' subflow allows to extract data from processing node.\r\n\r\n * Filter Data to extract only one variable after the \"processing node\" step .\r\n * Modify the message to fit with standard widgets such as gauges.\r\n * Limit the number of messages to a maximum of 10 msg per second.\r\n\r\n### **Input**\r\n\r\nThe subflow 'single value' takes in input the `msg.payload`\r\nfrom the processing output. All the messages sent by the processing node have the same structure.\r\n\r\n    \"payload\": {\r\n        \"groupname\": \"Snapshot2\",\r\n       \"variabledata\": [\r\n            {\r\n                \"y\": \"9\",\r\n               \"x\": \"1567509421459\"\r\n            },\r\n            {\r\n                \"y\": \"9\",\r\n               \"x\": \"1567509421459\"\r\n            },\r\n            {\r\n                \"y\": \"9\",\r\n                \"x\": \"1567509421459\"\r\n           }\r\n        ],\r\n        \"variablename\": \"int2\"\r\n      }\r\n    }\r\n\r\n### **Filter on selected variable**\r\nSeveral messages are sent by the processing node for each variable, so the first step of this 'single value' subflow is to filter and keep only messages related to the selected variable.\r\nThe 'filter variable' node will only keep messages with the right `variablename` key.\r\n\r\n### **Extract duplets table**\r\n\r\nThe received message is an Object containing {groupname, variabledata[array], variablename}.  \r\nThe array contains two variables :\r\n - `y` the value \r\n - `x` the time\r\n\r\nA single message contains multiple pairs of values extract at a regular time interval.\r\nThe time interval depends on the acquisition frequency.\r\nWith the 'change' node and the `set` property, the `msg.payload` is set to \r\n`msg.payload.variabledata`, only the data variable array is set into msg.payload.\r\n\r\n    \"payload\": {\r\n        \"variabledata\": [\r\n            {\r\n                \"y\": \"9\",\r\n               \"x\": \"1567509421459\"\r\n            },\r\n            {\r\n                \"y\": \"9\",\r\n               \"x\": \"1567509421459\"\r\n            },\r\n            {\r\n                \"y\": \"9\",\r\n                \"x\": \"1567509421459\"\r\n           }\r\n        ]\r\n    }\r\n\r\n### **Split duplets table**\r\n\r\nThe data variable array is split with a length of one in smaller objects by the 'split' node.\r\nEach new object contains two subvariables : `y` and `x`.\r\n\r\n    \"payload\": {\r\n        \"y\": \"9\",\r\n       \"x\": \"1567509421459\"\r\n    }\r\n\r\n\r\n### **Limit the number of messages**\r\n\r\nThe 'delay' node allows to limit the maximum number of message per second.\r\nIn the 'single value' subflow, one message is extracted per second.\r\n\r\n>_Be careful, a dashboard saturation risk exists if too many messages are received per second._\r\n\r\n### **Extract only y values**\r\n\r\nThe `msg.payload` is set to `msg.payload.y` with the `set` property. \r\nOnly `y` variable is set into msg.payload as single value.\r\n\r\n    \"payload\": { \r\n        \"y\": \"9\" \r\n    }\r\n\r\n### **Output** \r\n\r\nSome widgets such as the gauge and the text node are using the `msg.payload` property. \r\nThe output node is linked to a gauge that allows to see the evolution \r\nof the `y` variable as a function of time.\r\n\r\n\r\n![subflow_gauge](images/subflow_gauge.png)\r\n\r\n### **Details**\r\n\r\nNote : The debug node allows to see clearly and easily the \r\nmessage at the node output.  \r\n\r\n\r\n> More details on : [Node-Red](https://nodered.org/docs/user-guide/editor/workspace/subflows)\r\n\r\n\r\n-------\r\n\r\n","category":"","in":[{"x":40,"y":60,"wires":[{"id":"3c5ffe0d.9063b2"}]}],"out":[{"x":1080,"y":220,"wires":[{"id":"554b3d41.466564","port":1}]}],"env":[{"name":"varfilter","type":"str","value":"","ui":{"icon":"font-awesome/fa-filter","label":{"en-US":"Variable"},"type":"input","opts":{"types":["str"]}}}],"color":"#3CB4E6"},{"id":"3c5ffe0d.9063b2","type":"switch","z":"8381c865.236028","name":"filter variable","property":"payload.variablename","propertyType":"msg","rules":[{"t":"eq","v":"varfilter","vt":"env"}],"checkall":"true","repair":false,"outputs":1,"x":170,"y":60,"wires":[["4e5ba7a5.14dec8"]]},{"id":"4e5ba7a5.14dec8","type":"change","z":"8381c865.236028","name":"keep only table of duplets","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.variabledata","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":60,"wires":[["42bc6ed.5b20d9"]]},{"id":"42bc6ed.5b20d9","type":"split","z":"8381c865.236028","name":"Split a table[1..n] of duplets in n msg of single duplet","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1000,"y":40,"wires":[["66397fd3.d8111"]]},{"id":"66397fd3.d8111","type":"change","z":"8381c865.236028","name":"keep only value and delete timestamp","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.y","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":220,"wires":[["554b3d41.466564"]]},{"id":"554b3d41.466564","type":"switch","z":"8381c865.236028","name":"","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":790,"y":220,"wires":[[],[]]},{"id":"67fe38e2.cb8168","type":"tab","label":"Led_Flow","disabled":false,"info":"# Basic flow to display a LED with STM32CubeMonitor."},{"id":"c0f9657d.1b8b48","type":"acquisition out","z":"67fe38e2.cb8168","name":"myProbe_Out","probeconfig":"f9ea9fed.8109e","x":940,"y":520,"wires":[]},{"id":"d3ab7ca6.454fb","type":"acquisition in","z":"67fe38e2.cb8168","name":"myProbe_In","probeconfig":"f9ea9fed.8109e","x":130,"y":800,"wires":[["4d76b505.80167c"],[]]},{"id":"4d76b505.80167c","type":"processing","z":"67fe38e2.cb8168","groupname":"GPIO_LED","groupid":"a95a2ada.8f2398","expressions":[],"statistics":[],"logmode":"no","logformat":"stcm","x":370,"y":780,"wires":[["ae6ae64f.0bc5e8"],["9112a628.c9c3c8"]]},{"id":"ae6ae64f.0bc5e8","type":"subflow:8381c865.236028","z":"67fe38e2.cb8168","name":"GPIO Register","env":[{"name":"varfilter","value":"GPIOR","type":"str"}],"x":580,"y":760,"wires":[["8b2136fe.d59668"]]},{"id":"a0804b27.b00ab8","type":"http in","z":"67fe38e2.cb8168","name":"","url":"/cli","method":"get","upload":false,"swaggerDoc":"","x":70,"y":620,"wires":[["82369140.fc0a3"]]},{"id":"8289eb21.7596f8","type":"http response","z":"67fe38e2.cb8168","name":"","statusCode":"","headers":{},"x":950,"y":660,"wires":[]},{"id":"82369140.fc0a3","type":"switch","z":"67fe38e2.cb8168","name":"","property":"payload.cmd","propertyType":"msg","rules":[{"t":"eq","v":"set","vt":"str"},{"t":"eq","v":"read","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":3,"x":210,"y":620,"wires":[["f334ebdd.8d58b8","28151e12.829212"],["6f3507d0.054668","dabc553853f60d51"],["e7cf1fce.f8fc8"]]},{"id":"e7cf1fce.f8fc8","type":"template","z":"67fe38e2.cb8168","name":"unknown command","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Invalid command","x":550,"y":940,"wires":[["8289eb21.7596f8"]]},{"id":"13de08ed.3b8b37","type":"comment","z":"67fe38e2.cb8168","name":"receive HTTP request","info":"curl --silent \ncurl http://localhost:1880/cli?cmd=read\ncurl \"http://localhost:1880/cli?cmd=set&val=0x20\"\n\n","x":140,"y":560,"wires":[]},{"id":"8b2136fe.d59668","type":"join","z":"67fe38e2.cb8168","name":"","mode":"custom","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"","joinerType":"str","accumulate":false,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":690,"y":680,"wires":[["8289eb21.7596f8"]]},{"id":"9cc5ab2e.dd9ac8","type":"comment","z":"67fe38e2.cb8168","name":"commands to use in cli","info":"\n** Set the led on\ncurl \"http://localhost:1880/cli?cmd=set&addr=0x48000414&val=2\"\n\n** set the led off \ncurl \"http://localhost:1880/cli?cmd=set&addr=0x48000414&val=0\"\n\n** read the led state \ncurl \"http://localhost:1880/cli?cmd=read&addr=0x48000414\"\n\n** curl options :\n*** do not use proxy, conencted directly to localhost : --noproxy localhost \n*** do not print extra information :  --silent \n*** 5 seconds tiemout :  -m 5 \n","x":140,"y":480,"wires":[]},{"id":"b4359417.237c08","type":"comment","z":"67fe38e2.cb8168","name":"sendHTTP resp","info":"","x":940,"y":620,"wires":[]},{"id":"70aef67c.221758","type":"comment","z":"67fe38e2.cb8168","name":"Invalid command","info":"This answer is used when command is not valid.\nHTTP status is OK (200) but could be forced to error code.","x":540,"y":900,"wires":[]},{"id":"28151e12.829212","type":"template","z":"67fe38e2.cb8168","name":"Build HTTP answer ","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Write LED port : {{payload.val}}\n","output":"str","x":490,"y":500,"wires":[["8289eb21.7596f8"]]},{"id":"f334ebdd.8d58b8","type":"template","z":"67fe38e2.cb8168","name":"write payload","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"variablelist\": [\n        {\n            \"address\": \"{{payload.addr}}\",\n            \"type\":5,\n            \"value\": \"{{payload.val}}\"\n            }\n        ],\n        \"accesspoint\":0\n}","output":"json","x":470,"y":460,"wires":[["848fa752.138fe8"]]},{"id":"848fa752.138fe8","type":"change","z":"67fe38e2.cb8168","name":"write topic","rules":[{"t":"set","p":"topic","pt":"msg","to":"write","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":660,"y":460,"wires":[["c0f9657d.1b8b48"]]},{"id":"59c91115.f1f96","type":"change","z":"67fe38e2.cb8168","name":"read topic","rules":[{"t":"set","p":"topic","pt":"msg","to":"read","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":620,"wires":[["c0f9657d.1b8b48"]]},{"id":"6f3507d0.054668","type":"template","z":"67fe38e2.cb8168","name":"read payload","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"groupname\": \"GPIO_LED\",\n    \"variablelist\": [\n        {\n            \"name\":\"GPIOR\",\n            \"address\": \"{{payload.addr}}\",\n            \"type\":5\n            }\n        ],\n   \"accesspoint\":0,\n    \"mode\": \"direct\",\n    \"snapshotheader\": \"\",\n    \"triggerstartmode\": \"manual\",\n\"triggername\": \"ODR\",\n\"triggerthreshold\": \"30000\",\n\"frequency\": 1\n\n}","output":"json","x":470,"y":620,"wires":[["59c91115.f1f96"]]},{"id":"a95a2ada.8f2398","type":"variables","z":"67fe38e2.cb8168","groupname":"GPIO_LED","accesspoint":0,"execonfig":"","variablelist":[{"name":"GPIOR","address":"0x00000000","type":"5"}],"triggerstartmode":"manual","triggername":"GPIOR","triggerthreshold":"","frequency":"","frequencyType":"0.1","snapshotheader":"","mode":"direct","lastImportedTime":-1,"openStatus":true,"x":150,"y":740,"wires":[[],[]]},{"id":"1b073155.a72fff","type":"comment","z":"67fe38e2.cb8168","name":"read led state","info":"","x":470,"y":580,"wires":[]},{"id":"5b04abb5.e1eff4","type":"comment","z":"67fe38e2.cb8168","name":"write led gpio","info":"","x":470,"y":420,"wires":[]},{"id":"9112a628.c9c3c8","type":"debug","z":"67fe38e2.cb8168","name":"log errors","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":820,"wires":[]},{"id":"dabc553853f60d51","type":"template","z":"67fe38e2.cb8168","name":"Prepare HTTP answer ","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Read GPIO port {{payload.addr}} : \n","output":"str","x":500,"y":680,"wires":[["8b2136fe.d59668"]]},{"id":"f9ea9fed.8109e","type":"probe","probeid":"066DFF545057717867195835","probename":"ST-Link v2-1B 066DFF545057717867195835","protocol":"SWD","frequency":"1.8 MHz - Default","probeproperty":"{\"SWD\":[\"4.6 MHz\",\"1.8 MHz - Default\",\"950 kHz\",\"400 kHz\",\"150 kHz\"],\"JTAG\":[\"18 MHz\",\"9 MHz\",\"4.5 MHz\",\"2.25 MHz\",\"1.12 MHz - Default\",\"560 kHz\",\"280 kHz\",\"140 kHz\"]}","probeversion":"ST Link firmware version V2.J35","connectionType":"p2p","nickname":""}]


Turn the LED ON (on Nucleo-WB55)

>curl "http://localhost:1880/cli?cmd=set&addr=0x48000414&val=2"
Write LED port : 2

Read the ODR register (0x48000414)

>curl "http://localhost:1880/cli?cmd=read&addr=0x48000414"
Read GPIO port 0x48000414 :
2

Turn the LED OFF

>curl "http://localhost:1880/cli?cmd=set&addr=0x48000414&val=0"
Write LED port : 0

The flow is based on the structure described previously, but is now connected to a probe and a Nucelo board.

  • When a write request is received, a write payload is built and sent to the "probe OUT" node to write the value on the target, and the answer is sent to the "http response". The http syntax is cmd=set&addr=<register address>&val=<value to write>
  • When a read request is received, a read instruction is built and sent to "probe OUT". Then when the read value is returned by the "probe IN", the value is extracted by the processing node and joined to the original request to send the answer. The http command is cmd=read&addr=<register address>

The same code can be used to access other registers or memory location.

6. Conclusion

With these flows, it is possible to create an interaction between scripts and STM32CubeMonitor. An external script or program sends http request to STM32CubeMonitors to trig actions and to get some results.

No categories assignedEdit