This page explains how to start some actions from an external script.
1. Introduction
STM32CubeMonitor provides the environment to create flows to build dashboards or manage actions. Using the finite state machine provided in the Node-RED® ecosystem, it is possible to build complex automatized processes for dashboards or to drive actions on STM32 devices. It is also possible to execute "command line" actions through the exec node. As a result, most of the time, STM32CubeMonitor is capable of managing measurements automatically.
Some users might also need to drive their actions from an external script (such as .bat, .sh, or others) or an external tool. In such cases, interacting with the graphical user interface of STM32CubeMonitor is not efficient. This page explains how it is possible to use the "http" node to let a script or an external application trigger actions in the STM32CubeMonitor flow.
2. The concept
As soon as STM32CubeMonitor is started, the existing flows are running. Even if the dashboard page is not open, flows are working in background. The solution is to add in the flow one http node 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.
3. The HTTP node
The HTTP node is used to send or receive HTTP requests in Node-RED®. For the actions from script case, the "http in" and "http response" nodes are used.
The "http in" node will receive an 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. Deploy the flows.
After deploying the flow, a command line is used : curl "http://localhost:1880/demo?cmd=test1¶m=hello". (It is also possible to put "http://localhost:1880/demo?cmd=test1¶m=hello" in a web browser.)
The result should be
>curl "http://localhost:1880/demo?cmd=test1¶m=hello"
The command is : test1
The parameter is : hello
The elements after the ? are parameters. In this example there is a parameter "cmd" and another parameter "param. The "template" node use the parameters to build the answer. The names are free and the parameters can be used for any purpose.
4. The http actions implementation
The previous example is just illustrating the http nodes, but it is not very useful. Here is a more complete flow showing how to manage multiple commands, with immediate or delayed responses.
[{"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¶m=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¶m=hello%20world\"\n\n2) command with delayed answer\n curl \"http://localhost:1880/cli?cmd=test2¶m=test\"\n \n 3) Invalid command \n curl \"http://localhost:1880/cli?cmd=test3¶m=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, a first 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 first exemple : the answer is prepared and sent immediately to requester. It can be useful if some information are already known in the flow and should be returned (note that the flow is always running in background, and data could be stored in a flow "context")
- The second part is for a command which requires some processing time. In this case the user must click on the "inject" node to send the result. The http request in console is waiting until user clicks 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 "join" node to merge 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 an error message is sent immediately to unblock the console.
>curl "http://localhost:1880/cli?cmd=test1¶m=hello%20world"
The command is test1
The parameter is : hello world
>curl "http://localhost:1880/cli?cmd=test2¶m=867"
This is the result
>curl "http://localhost:1880/cli?cmd=test3"
Invalid command
This example can be adapted to trigger 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 allows 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 with the register value to write.
[{"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 "processin"g 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 examples, it is possible to create an interaction between scripts and STM32CubeMonitor. An external script or program sends http request to STM32CubeMonitors to trigger actions, the flow process the request and send back the results.