How to PUT a file into groov storage via the Manage REST API

In this post I’ll break down how to build a flow that uses the HTTP request node to PUT a file (a *.png image file in this case) into the unsecured file area on a groov EPIC – but this will also work with the secured file area and USB storage on EPICs and groov RIOs as well.

The two main sources for this are the groov Manage API Swagger page and the help tab of the HTTP request node:

image

The Manage API Swagger page tells us the "KEY" should be "file", the FILE_CONTENTS should be the file binary buffer, and "FILENAME" is of course the filename. Along with that we need to set msg.headers to have both "content-type" and "apiKey" properties.
The last part is the URL, again referring to the Swagger UI we can see the endpoint is "/api/v1/files/{area}/rpc/create-file/{areaPath}" where {area} should be ‘unsecured’, ‘secured’, or ‘usb’, and {areaPath} is the folder and filename within that area.

For example to just create a file “hello.txt” directly in the unsecured file area of the localhost device, use the following URL:
https://localhost/manage/api/v1/files/unsecured/rpc/create-file/hello.txt

To create a new folder or add to an existing folder, just include that in the {areaPath} like this:
https://localhost/manage/api/v1/files/unsecured/rpc/create-file/folder/hello.txt


All together, it ends up looking something like this, where I’m reading an image file into my flow, then writing it to the unsecured file area:

Using the following JavaScript code in the “set properties” function node:

msg.payload = {
    "file": {
        "value": msg.payload,
        "options": {
            "filename": msg.filename
        }
    }
}
msg.headers = {
    "apiKey":"mR4VG3sRSrRJok4ZouNGJN7iNJQpfKER",
    "Content-Type":"multipart/form-data"
}
msg.url = "https://localhost/manage/api/v1/files/unsecured/rpc/create-file/folder/image.png";
return msg;

One final note is that to make the HTTPS request you will need to enable the secure (SSL/TLS) connection option in the http request node, which is covered in this forum post: Authenticating Opto API calls with HTTP request

If you have any questions about this, or end up modifying this or using it in a project, please drop a line in the thread below.
And as always, happy flowing!


Flow import JSON:

[{"id":"d57e4f633753da9d","type":"tab","label":"HTTP PUT Example","disabled":false,"info":""},{"id":"534f4bec03ec7ecc","type":"inject","z":"d57e4f633753da9d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":60,"wires":[["e7f55d7be760072e"]]},{"id":"e7f55d7be760072e","type":"file in","z":"d57e4f633753da9d","name":"input.png","filename":"/home/dev/unsecured/input.png","format":"","chunk":false,"sendError":false,"encoding":"base64","allProps":false,"x":280,"y":60,"wires":[["b90a33f234962d78"]]},{"id":"8fde62bcb5a56053","type":"http request","z":"d57e4f633753da9d","name":"","method":"PUT","ret":"obj","paytoqs":"ignore","url":"","tls":"39d8eb96e6ac28c0","persist":false,"proxy":"","authType":"","credentials":{},"x":610,"y":60,"wires":[["7d6dcc5cabc9e65f"]]},{"id":"7d6dcc5cabc9e65f","type":"debug","z":"d57e4f633753da9d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":750,"y":60,"wires":[]},{"id":"b90a33f234962d78","type":"function","z":"d57e4f633753da9d","name":"set properties","func":"msg.payload = {\n    \"file\": {\n        \"value\": msg.payload,\n        \"options\": {\n            \"filename\": msg.filename\n        }\n    }\n}\nmsg.headers = {\n    \"apiKey\":\"mR4VG3sRSrRJok4ZouNGJN7iNJQpfKER\",\n    \"Content-Type\":\"multipart/form-data\"\n}\nmsg.url = \"https://localhost/manage/api/v1/files/unsecured/rpc/create-file/folder/image.png\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":60,"wires":[["8fde62bcb5a56053"]]},{"id":"39d8eb96e6ac28c0","type":"tls-config","name":"","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false,"alpnprotocol":""}]

Are there plans to allow file upload and download from groov View? This would be nice for things like recipes so the end user doesn’t have to go out of the groov environment.

Thanks @torchard this worked perfectly for us!
Grabbing an image from a camera, placing it in groov storage on a PR2, then sending it elsewhere via MQTT (Ignition Edge). Code for anyone interested:

[{"id":"27e7941ba0f4319b","type":"tab","label":"mqtt photo snatcher","disabled":false,"info":""},{"id":"e9407de2ac04f35d","type":"http request","z":"27e7941ba0f4319b","name":"photo getter","method":"GET","ret":"bin","paytoqs":"ignore","url":"http://192.168.1.48/cgi-bin/snapshot.cgi","tls":"","persist":false,"proxy":"","authType":"digest","credentials":{},"x":310,"y":160,"wires":[["4bc5705ec9664874"]]},{"id":"5baf9c464bdfb11d","type":"OpcUa-Item","z":"27e7941ba0f4319b","item":"ns=4;s=|var|Opto22-Cortex-Linux.Application.ILC_VPN.xCamera","datatype":"Boolean","value":"","name":"xCamera","x":300,"y":80,"wires":[["222a7ed6e5308e34"]]},{"id":"222a7ed6e5308e34","type":"OpcUa-Client","z":"27e7941ba0f4319b","endpoint":"f0e7ae310d14b492","action":"subscribe","deadbandtype":"a","deadbandvalue":1,"time":"100","timeUnit":"ms","certificate":"n","localfile":"","localkeyfile":"","securitymode":"None","securitypolicy":"None","folderName4PKI":"","name":"codesys opc","x":470,"y":80,"wires":[["b9789b1222339fd0"]]},{"id":"860e178d7c5dcd94","type":"inject","z":"27e7941ba0f4319b","name":"run this mutha","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payloadType":"date","x":140,"y":80,"wires":[["5baf9c464bdfb11d"]]},{"id":"4bc5705ec9664874","type":"function","z":"27e7941ba0f4319b","name":"file compile","func":"msg.payload = {\n    \"file\": {\n        \"value\": msg.payload,\n        \"options\": {\n            \"filename\": \"snapshot.jpeg\"\n        }\n    }\n}\nmsg.headers = {\n    \"apiKey\":\"YOUR KEY NOT MINE\",\n    \"Content-Type\":\"multipart/form-data\"\n}\nmsg.url = \"https://localhost/manage/api/v1/files/unsecured/rpc/create-file/snapshot.jpeg\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":160,"wires":[["4fea372a52b7e7e7"]]},{"id":"b9789b1222339fd0","type":"switch","z":"27e7941ba0f4319b","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":150,"y":160,"wires":[["e9407de2ac04f35d"]]},{"id":"4fea372a52b7e7e7","type":"http request","z":"27e7941ba0f4319b","name":"photo putter","method":"PUT","ret":"txt","paytoqs":"ignore","url":"","tls":"ee5eeb5ca588a2f9","persist":false,"proxy":"","authType":"","credentials":{},"x":670,"y":160,"wires":[["52ec28f86270b2f0"]]},{"id":"44d01bd87f907845","type":"exec","z":"27e7941ba0f4319b","command":"cd /home/dev/unsecured/; md5sum snapshot.jpeg | awk '{printf $1}' > snapshot.jpeg.md5; chmod 664 snapshot.jpeg.md5","addpay":"","append":"","useSpawn":"false","timer":"1","winHide":false,"oldrc":false,"name":"md5 maker","x":330,"y":240,"wires":[["f6b955e3bf0b22d8"],[],[]]},{"id":"52ec28f86270b2f0","type":"switch","z":"27e7941ba0f4319b","name":"","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":150,"y":240,"wires":[["44d01bd87f907845"]]},{"id":"f59a3cd256e88d29","type":"OpcUa-Client","z":"27e7941ba0f4319b","endpoint":"f0e7ae310d14b492","action":"write","deadbandtype":"a","deadbandvalue":1,"time":10,"timeUnit":"s","certificate":"n","localfile":"","localkeyfile":"","securitymode":"None","securitypolicy":"None","folderName4PKI":"","name":"","x":660,"y":360,"wires":[[]]},{"id":"cc3a4b0f586a65c4","type":"function","z":"27e7941ba0f4319b","name":"","func":"msg={\"topic\":\"ns=4;s=|var|Opto22-Cortex-Linux.Application.ILC_VPN.xCamera\",\n    \"datatype\":\"Boolean\"\n}\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":470,"y":360,"wires":[["f59a3cd256e88d29"]]},{"id":"f6b955e3bf0b22d8","type":"switch","z":"27e7941ba0f4319b","name":"","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":570,"y":240,"wires":[["cc3a4b0f586a65c4"]]},{"id":"f0e7ae310d14b492","type":"OpcUa-Endpoint","endpoint":"opc.tcp://localhost:4840","secpol":"None","secmode":"None","none":false,"login":false,"usercert":false,"usercertificate":"","userprivatekey":""},{"id":"ee5eeb5ca588a2f9","type":"tls-config","name":"tls","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false,"alpnprotocol":"","credentials":{}}]
2 Likes

Great to hear it’s working for you!
And thankyou for sharing your flow, it looks like you have the MD5 hash working as well – very cool!

Ha! Had to do some shenanigans to create the file just how Transmission wants it, then change some file permissions so Ignition can delete after sending.

Also so odd behavior with the OPCUA-Item node, we couldn’t get it to write back to the Bool, so we wound up building our own payload. …probably not the cleanest, but functional!

1 Like