ESP8266 OTA Firmware Upgrade Manager
This flow provides a tool for managing Over The Air firmware updates for ESP8266 Wifi SoC modules running the Arduino ESP8266 environment. It also supports Sonoff devices running the Tasmota firmware.
The ESP8266 is a very small, cheap, and reasonably powerful microcontroller with integrated WiFi. The OTA firmware upgrade process allows you to install these modules in location and have them automatically upgrade their firmware over WiFi.
The OTA upgrade library contacts a specified URL and passes the name and MD5 hash of the module's current firmware. This server implemented in this flow will compare the supplied MD5 hash against the one for the latest firmware and, if different, send the new firmware to the module. The server will uses either the module's MAC address or firmware name to identify the correct firmware file to send. The firmware can be specified in either the request URL or in the x-esp8266-version property in the request header.
This flow also includes a firmware management screen for uploading new firmware files, deleting firmware files, and creating symbolic links to existing firmware files. Symbolic links allow you to point a specific MAC address to a firmware file so as to be able to replace the firmware on a module with a completely different one.
The firmware manager is accessed from http://your.node-red.server:1880/esp8266-ota
A template for producing firmware with OTA upgrade built in is available from this gist. This template checks for a firmware update on each boot up. It then runs a process such as reading a sensor and publishing the value, before entering a power saving deep sleep. When it wakes, it will check for a firmware upgrade again and repeat the cycle.
The Sonoff TASMOTA firmware is also supported. Set the OTA URL to be the full path of the firmware, ie. http://your.node-red.server:1880/esp8266-ota/update/sonoff.ino.generic
You will need to use version 2.4.1 or greater of the Arduino ESP8266 core.
[{"id":"c8dc8a91.74103","type":"tab","label":"ESP8266"},{"id":"ff59207a.78407","type":"http in","z":"c8dc8a91.74103","name":"OTA Request","url":"/esp8266-ota/update/:version","method":"get","upload":false,"swaggerDoc":"","x":84.5,"y":115,"wires":[["d3b826bb.dc20c8","eaeeef87.2b0bf"]]},{"id":"eeb8fd90.56a408","type":"http response","z":"c8dc8a91.74103","name":"OTA Response","x":3348.5597534179688,"y":594.107177734375,"wires":[]},{"id":"34f9b5d5.3b5d2a","type":"file in","z":"c8dc8a91.74103","name":"Load Firmware","filename":"","format":"","sendError":false,"x":2586.499786376953,"y":379.9285888671875,"wires":[["eeb8fd90.56a408","84851e19.4a5868"]]},{"id":"83413646.c2dd4","type":"change","z":"c8dc8a91.74103","name":"No Update","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"304","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2955.607421875,"y":238.5714111328125,"wires":[["eeb8fd90.56a408"]]},{"id":"9eb7afe3.46c438","type":"change","z":"c8dc8a91.74103","name":"Forbidden","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"403","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"Forbidden","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2933.499755859375,"y":115.89285278320312,"wires":[["eeb8fd90.56a408"]]},{"id":"d3b826bb.dc20c8","type":"switch","z":"c8dc8a91.74103","name":"Check user agent","property":"req.headers.user-agent","propertyType":"msg","rules":[{"t":"neq","v":"ESP8266-http-Update","vt":"str"},{"t":"else"}],"checkall":"false","outputs":2,"x":303.71435546875,"y":114.99999237060547,"wires":[["9eb7afe3.46c438"],["76e0c603.3b90e"]]},{"id":"76e0c603.3b90e","type":"switch","z":"c8dc8a91.74103","name":"Update type","property":"req.headers.x-esp8266-mode","propertyType":"msg","rules":[{"t":"eq","v":"spiffs","vt":"str"},{"t":"eq","v":"sketch","vt":"str"}],"checkall":"false","outputs":2,"x":484.0179748535156,"y":208.57144165039062,"wires":[["83413646.c2dd4"],["5a91d95d.b3f47"]]},{"id":"11ff4157.023e3f","type":"comment","z":"c8dc8a91.74103","name":"Sketch","info":"","x":650.7857055664062,"y":293.142822265625,"wires":[]},{"id":"67da418c.27d3c","type":"comment","z":"c8dc8a91.74103","name":"Spiffs","info":"","x":599.5,"y":157,"wires":[]},{"id":"12caf8cf.3a330f","type":"http in","z":"c8dc8a91.74103","name":"OTA Manager","url":"/esp8266-ota","method":"get","swaggerDoc":"","x":88.05558013916016,"y":648.2856712341309,"wires":[["5ae37750.4cb7a"]]},{"id":"499fe21f.f66814","type":"httpInMultipart","z":"c8dc8a91.74103","name":"OTA Upload","url":"/esp8266-ota","method":"post","fields":"[{ \"name\": \"firmware\"}]","swaggerDoc":"","x":79.7857437133789,"y":812.4286108016968,"wires":[["8951ec5b.14d068"]]},{"id":"8951ec5b.14d068","type":"fs-ops-move","z":"c8dc8a91.74103","name":"Store firmware","sourcePath":"req.files.firmware[0].destination","sourcePathType":"msg","sourceFilename":"req.files.firmware[0].filename","sourceFilenameType":"msg","destPath":"firmware.directory","destPathType":"flow","destFilename":"req.files.firmware[0].originalname","destFilenameType":"msg","link":false,"x":365.89284896850586,"y":818.3332557678223,"wires":[["8cd5701d.92156","eeb8fd90.56a408"]]},{"id":"b95b8c19.19c4e8","type":"md5","z":"c8dc8a91.74103","name":"","fieldToHash":"payload","hashField":"payload","hashFieldType":"msg","x":1031.6668090820312,"y":846.6666259765625,"wires":[["b3a0c31.a959d4"]]},{"id":"a4a110d9.d17838","type":"file","z":"c8dc8a91.74103","name":"Save md5 Checksum","filename":"","appendNewline":false,"createDir":false,"overwriteFile":"true","x":1539.9998168945312,"y":845.8333740234375,"wires":[]},{"id":"ded265c5.515f68","type":"file in","z":"c8dc8a91.74103","name":"Read firmware","filename":"","format":"","x":842.0833740234375,"y":846.6666870117188,"wires":[["b95b8c19.19c4e8"]]},{"id":"8cd5701d.92156","type":"change","z":"c8dc8a91.74103","name":"Extract filename","rules":[{"t":"set","p":"filename","pt":"msg","to":"firmware.directory","tot":"flow"},{"t":"change","p":"filename","pt":"msg","from":"$","fromt":"re","to":"req.files.firmware[0].originalname","tot":"msg"},{"t":"delete","p":"req","pt":"msg"},{"t":"delete","p":"res","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":619.375,"y":847.9166870117188,"wires":[["ded265c5.515f68"]]},{"id":"b3a0c31.a959d4","type":"change","z":"c8dc8a91.74103","name":"","rules":[{"t":"change","p":"filename","pt":"msg","from":"bin$","fromt":"re","to":"md5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1225.625,"y":846.25,"wires":[["a4a110d9.d17838"]]},{"id":"bf8f43a5.95f42","type":"file in","z":"c8dc8a91.74103","name":"Load Hash","filename":"","format":"utf8","sendError":false,"x":1743.910629272461,"y":321.82147216796875,"wires":[["ee5832d4.7db41"]]},{"id":"ee5832d4.7db41","type":"switch","z":"c8dc8a91.74103","name":"Compare MD5 hash","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"req.headers.x-esp8266-sketch-md5","vt":"msg"},{"t":"else"}],"checkall":"false","outputs":2,"x":1954.91064453125,"y":322.1786193847656,"wires":[["83413646.c2dd4"],["c19457f8.014dc8"]]},{"id":"c19457f8.014dc8","type":"change","z":"c8dc8a91.74103","name":"Save MD5 Hash","rules":[{"t":"set","p":"headers.x-MD5","pt":"msg","to":"payload","tot":"msg"},{"t":"change","p":"filename","pt":"msg","from":"md5","fromt":"str","to":"bin","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2279.2857360839844,"y":377.0714416503906,"wires":[["34f9b5d5.3b5d2a"]]},{"id":"70f9cdfb.519584","type":"fs-ops-access","z":"c8dc8a91.74103","name":"Check for MD5 file exists","path":"","pathType":"str","filename":"filename","filenameType":"msg","read":true,"write":false,"throwerror":false,"x":1028.857177734375,"y":329.1428527832031,"wires":[["bf8f43a5.95f42"],["196e6dc4.7e4242"]]},{"id":"5a91d95d.b3f47","type":"change","z":"c8dc8a91.74103","name":"Get MAC hash filename","rules":[{"t":"set","p":"filename","pt":"msg","to":"firmware.directory","tot":"flow"},{"t":"change","p":"filename","pt":"msg","from":"$","fromt":"re","to":"req.headers.x-esp8266-sta-mac","tot":"msg"},{"t":"change","p":"filename","pt":"msg","from":"$","fromt":"re","to":".md5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":723.1428451538086,"y":334.99999809265137,"wires":[["70f9cdfb.519584"]]},{"id":"4f93ee35.d787f","type":"comment","z":"c8dc8a91.74103","name":"Firmware Upgrade","info":"The system determines which firmware to send based\non the MAC address, version, and MD5 headers in the\nrequest from the ESP8266.\n\nFirstly, it checks if a firmware hash file with \nthe MAC address as the name exists, or else if a \nfirmware file with the version as the name exists.\nIf neither exist then no update is available.\n\nIt then reads the corresponding MD5 hash from the\nhash file and compares to the MD5 hash in the\nrequest. If they match then the firmware is up\nto date and no update is required.\n\nOtherwise, it sends the contents of the firmware\nbin file and the MD5 hash value.","x":103.5,"y":46,"wires":[]},{"id":"77e3a5f0.24ed14","type":"fs-ops-access","z":"c8dc8a91.74103","name":"Check for MD5 file exists","path":"","pathType":"str","filename":"filename","filenameType":"msg","read":true,"write":false,"throwerror":false,"x":1496.7857055664062,"y":439.7142028808594,"wires":[["bf8f43a5.95f42"],["83413646.c2dd4"]]},{"id":"1f741000.bf355","type":"comment","z":"c8dc8a91.74103","name":"Firmware Management","info":"","x":110.5,"y":542,"wires":[]},{"id":"9a3fb2c8.fdeae","type":"config","z":"c8dc8a91.74103","name":"Firmware Config","properties":[{"p":"firmware.directory","pt":"flow","to":"/home/sysadmin/firmware/","tot":"str"}],"active":true,"x":123.0714111328125,"y":360.1428527832031,"wires":[]},{"id":"67a99e0a.a70ae8","type":"change","z":"c8dc8a91.74103","name":"Get Version hash filename","rules":[{"t":"set","p":"filename","pt":"msg","to":"firmware.directory","tot":"flow"},{"t":"change","p":"filename","pt":"msg","from":"$","fromt":"re","to":"req.headers.x-esp8266-version","tot":"msg"},{"t":"change","p":"filename","pt":"msg","from":"$","fromt":"re","to":".md5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1203.2857666015625,"y":439.6427917480469,"wires":[["77e3a5f0.24ed14"]]},{"id":"5ae37750.4cb7a","type":"fs-ops-dir","z":"c8dc8a91.74103","name":"Firmware listing","path":"firmware.directory","pathType":"flow","filter":"*.bin","filterType":"str","dir":"payload.files","dirType":"msg","x":493.0355987548828,"y":647.8015928268433,"wires":[["f6002008.43daa8"]]},{"id":"dd6fee12.904a58","type":"template","z":"c8dc8a91.74103","name":"Firmware Manager","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<html>\n <head>\n <title>ESP8266 OTA Firmware Manager</title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <link rel=\"stylesheet\" href=\"http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css\">\n <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js\"></script>\n <script src=\"http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js\"></script>\n <style>\n .file-btn-grp {\n display: none;\n }\n body {\n background-color: #f6f6f6;\n }\n .text_holder {\n display: block;\n max-width: 100%;\n word-wrap: break-word;\n line-height: 30px;\n }\n #main {\n margin-top: 50px;\n background-color: #ffffff;\n border-radius: 5px;\n width: 70%;\n min-width: 500px;\n border: 1px solid #545454;\n }\n .alert {\n margin-top: 10px;\n margin-bottom: 5px;\n }\n .link {\n font-style: italic;\n }\n .listentry {\n display: none;\n list-style-type: none;\n }\n </style>\n\n </head>\n <body>\n <div class=\"container\" id=\"main\">\n <h1><small>ESP8266 OTA Firmware Manager</small></h1>\n <h2><small>Upload New Firmware</small></h2>\n\n <form role =\"form\" id=\"main_input_box\" action=\"esp8266-ota\" method=\"POST\" enctype=\"multipart/form-data\" class=\"form-inline\">\n <input type=\"file\" accept=\".bin\" class=\"hidden\" id=\"fileUpload\" name=\"firmware\" placeholder=\"firmware.bin\">\n <div class=\"form-group\">\n <div class=\"input-group\">\n <span class=\"input-group-btn\">\n <button id=\"browse\" type=\"button\" class=\"btn btn-primary\">Browse...</button>\n </span>\n <input id=\"filename\" size=100 readonly=\"\" class=\"form-control\" placeholder=\"Firmware.bin\">\n <span class=\"input-group-btn\">\n <input type=\"submit\" value=\"Upload\" class=\"btn btn-primary add_button\">\n </span>\n </div>\n </div>\n </form>\n\n <h2><small>Current Firmware</small></h2>\n <ul class=\"list-group\" id=\"list_of_items\">\n </ul>\n </div>\n\n <div id=\"linkModal\" class=\"modal fade\" role=\"dialog\">\n <div class=\"modal-dialog\">\n \n <!-- Modal content-->\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <button type=\"button\" class=\"close\" data-dismiss=\"modal\">×</button>\n <h4 class=\"modal-title\">Create Symbolic Link</h4>\n </div>\n <div class=\"modal-body\">\n <form class=\"form-horizontal\" id=\"linkForm\" role=\"form\" action=\"#\" method=\"POST\">\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"linkName\">Link:</label> \n <div class=\"col-sm-10\">\n <input id=\"linkName\" name=\"linkName\" class=\"form-control\"/>\n </div>\n </div>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"linkFile\">To:</label> \n <div class=\"col-sm-10\">\n <input class=\"form-control\" id=\"linkFile\" name=\"linkFile\"/>\n </div>\n </div>\n </form>\n </div>\n <div class=\"modal-footer\">\n <button type=\"submit\" class=\"btn btn-success link-create\">Create</button>\n <button type=\"button\" class=\"btn btn-warning\" data-dismiss=\"modal\">Cancel</button>\n </div>\n </div>\n </div>\n </div>\n \n <script>\n\n var deleteButton = \"<button id='delete' class='btn btn-warning'>Delete</button>\";\n var linkButton = \"<button id='link' class='btn btn-success'>Link</button>\";\n var oneButton = \"<div class='file-btn-grp btn-group pull-right'>\" + deleteButton + \"</div>\";\n var twoButtons = \"<div class='file-btn-grp btn-group pull-right'>\" + deleteButton + linkButton + \"</div>\";\n \n function addListEntry(list, filename, linkname) {\n \n var entry;\n \n if (linkname === undefined) {\n entry = $(\"<li class='listentry list-group-item'><div class='text_holder'><span>\" \n + filename + \"</span>\" + twoButtons + \"</div></li>\");\n } else {\n entry = $(\"<li class='listentry list-group-item'><div class='text_holder'><span class='link'>\" \n + filename + \"</span> <span class='glyphicon glyphicon-arrow-right'></span> \" \n + linkname + oneButton + \"</div></li>\");\n }\n \n entry.mouseover(function(){\n $(this).find(\".btn-group\").fadeIn(\"fast\");\n });\n entry.mouseleave(function(){\n $(this).find(\".btn-group\").fadeOut(\"slow\");\n });\n \n entry.on(\"click\", \"button#delete\", function(){\n var listItem = this.parentElement.parentElement.parentElement;\n var file = this.parentElement.parentElement.childNodes[0].childNodes[0].data;\n $.ajax({\n type: 'DELETE',\n data: {\"filename\": file},\n success: function(result) {\n $(listItem).fadeOut(\"slow\", function(){\n listItem.remove();\n });\n },\n error: function(result) {\n $(listItem).append(\"<div class='alert alert-danger'>\"\n + \"<a href='#' class='close' data-dismiss='alert'\"\n + \"aria-label='close'>×</a>\"\n + result.responseText + \"</div>\");\n }\n });\n });\n \n entry.on(\"click\", \"button#link\", function(){\n var file = this.parentElement.parentElement.childNodes[0].childNodes[0].data;\n $(\"#linkName\").val(\"\");\n $(\"#linkFile\").prop(\"disabled\", true).val(file);\n $(\"#linkName\").parent().removeClass(\"has-error\");\n $(\"#linkModal\").modal();\n $('#linkModal').on('shown.bs.modal', function () {\n $('#linkName').focus();\n });\n });\n \n if (list.children().length == 0) {\n list.append(entry);\n entry.fadeIn(\"slow\");\n return;\n }\n\n if (filename < $(\"li:first span:first\", list).text()) {\n $(\"li:first\", list).before(entry);\n entry.fadeIn(\"slow\");\n return;\n }\n if (filename > $(\"li:last span:first\", list).text()) {\n $(\"li:last\", list).after(entry);\n entry.fadeIn(\"slow\");\n return;\n }\n \n var count = 0;\n var $li = $(\"li\", list);\n do {\n var index = parseInt($li.length / 2);\n var $compare = $li.eq(index);\n var compare = $(\"span:first\",$compare).text();\n if (filename === compare) {\n break;\n }\n else if (filename > compare) {\n $li = $li.slice(index, $li.length);\n } else {\n $li = $li.slice(0, index);\n }\n } while ($li.length > 1);\n\n if (filename === compare) {\n $compare.slideUp(\"fast\").slideDown(\"slow\");\n return;\n } else if (filename < compare) { \n entry.insertBefore($compare); \n } else { \n entry.insertAfter($compare); \n }\n entry.slideDown(\"slow\");\n }\n \n \n var files = String(\"{{payload.files}}\").split(',');\n var links = String(\"{{payload.links}}\").split(',');\n \n var list_of_items = $(\"#list_of_items\");\n \n files.forEach(function(file,index) {\n if (links[index] == \"\") {\n addListEntry(this, file.slice(0,-4));\n } else {\n addListEntry(this, file.slice(0,-4), links[index].slice(0,-4));\n }\n },list_of_items);\n \n \n $(\"#linkModal\").on(\"click\", \"button.link-create\", function(event){\n event.preventDefault();\n if ($(\"#linkName\").val() == \"\") {\n $('#linkName').focus();\n $('#linkName').parent().addClass(\"has-error\");\n return false;\n }\n $(\"#linkFile\").prop(\"disabled\",false); \n $.ajax({\n type: 'PUT',\n data: $(\"#linkForm\").serialize(),\n success: function(result) {\n $(\"#linkModal\").modal('hide');\n addListEntry(list_of_items, $('#linkName').val(), $('#linkFile').val());\n },\n error: function(result) {\n $(\"#linkFile\").prop(\"disabled\", true);\n $('#linkName').focus();\n $('#linkName').parent().addClass(\"has-error\");\n $(linkForm).append(\"<div id='linkError' class='alert alert-danger'>\"\n + \"<a href='#' class='close' data-dismiss='alert'\"\n + \"aria-label='close'>×</a>\"\n + result.responseText + \"</div>\");\n }\n });\n });\n \n $(\"#browse\").on('click', function () {\n fileUpload.click();\n });\n \n $(\"#fileUpload\").on('change', function () {\n $(\"#filename\").val($(\"#fileUpload\").val().split('/').pop().split('\\\\').pop());\n });\n \n var frm = $(\"#main_input_box\");\n \n frm.submit( function( e ) {\n e.preventDefault();\n if ($('#fileUpload').val() == \"\") return null;\n $.ajax( {\n url: frm.attr('action'),\n type: frm.attr('method'),\n data: new FormData( this ),\n processData: false,\n contentType: false,\n success: function(result) {\n addListEntry(list_of_items, $('#fileUpload').val().slice(0,-4).split('\\\\').pop());\n $('#fileUpload').val('');\n $(':text').val('');\n },\n error: function(result) {\n $(\"#main_input_box\").append(\"<div class='alert alert-danger'>\"\n + \"<a href='#' class='close' data-dismiss='alert'\"\n + \"aria-label='close'>×</a>\"\n + result.responseText + \"</div>\");\n }\n\n });\n });\n </script>\n </body>\n</html>","output":"str","x":1381.3333740234375,"y":645.8135375976562,"wires":[["eeb8fd90.56a408"]]},{"id":"daac23b5.56202","type":"http in","z":"c8dc8a91.74103","name":"OTA Delete","url":"/esp8266-ota","method":"delete","swaggerDoc":"","x":83.125,"y":933.75,"wires":[["b598cd93.0da24"]]},{"id":"eea8a4b7.82d8e","type":"fs-ops-delete","z":"c8dc8a91.74103","name":"Delete firmware","path":"firmware.directory","pathType":"flow","filename":"payload.filename","filenameType":"msg","x":1628.6250457763672,"y":936.5832777023315,"wires":[["e7711371.64d218"]]},{"id":"ecef5877.cde138","type":"catch","z":"c8dc8a91.74103","name":"File Error","scope":["eea8a4b7.82d8e","2c0afcd2.18843c","b6713897.2c6f58","95667d64.83a058","8951ec5b.14d068"],"x":1870.6665573120117,"y":805.3333158493042,"wires":[["b0e409f8.1fb4e8"]]},{"id":"b0e409f8.1fb4e8","type":"change","z":"c8dc8a91.74103","name":"Error","rules":[{"t":"set","p":"statusCode","pt":"msg","to":"500","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"error.message","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2159,"y":791,"wires":[["eeb8fd90.56a408"]]},{"id":"e7711371.64d218","type":"change","z":"c8dc8a91.74103","name":"Change to MD5 filename","rules":[{"t":"change","p":"payload.filename","pt":"msg","from":"bin","fromt":"str","to":"md5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1879.1666564941406,"y":936.6666269302368,"wires":[["2c0afcd2.18843c"]]},{"id":"2c0afcd2.18843c","type":"fs-ops-delete","z":"c8dc8a91.74103","name":"Delete MD5 Hash","path":"firmware.directory","pathType":"flow","filename":"payload.filename","filenameType":"msg","x":2170.833206176758,"y":936.6665878295898,"wires":[["eeb8fd90.56a408"]]},{"id":"b6713897.2c6f58","type":"fs-ops-move","z":"c8dc8a91.74103","name":"Link firmware","sourcePath":"","sourcePathType":"str","sourceFilename":"payload.linkFile","sourceFilenameType":"msg","destPath":"firmware.directory","destPathType":"flow","destFilename":"payload.linkName","destFilenameType":"msg","link":true,"x":1644.5,"y":1044,"wires":[["f075e122.d9acf"]]},{"id":"f80cd2dd.cf0098","type":"http in","z":"c8dc8a91.74103","name":"OTA Link","url":"/esp8266-ota","method":"put","swaggerDoc":"","x":84,"y":1041.5,"wires":[["119e425c.4ead7e"]]},{"id":"119e425c.4ead7e","type":"change","z":"c8dc8a91.74103","name":"Extract filename","rules":[{"t":"change","p":"payload.linkName","pt":"msg","from":"$","fromt":"re","to":".bin","tot":"str"},{"t":"change","p":"payload.linkFile","pt":"msg","from":"$","fromt":"re","to":".bin","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1385,"y":1044,"wires":[["b6713897.2c6f58"]]},{"id":"f075e122.d9acf","type":"change","z":"c8dc8a91.74103","name":"Change to MD5 Filename","rules":[{"t":"change","p":"payload.linkName","pt":"msg","from":"bin$","fromt":"re","to":"md5","tot":"str"},{"t":"change","p":"payload.linkFile","pt":"msg","from":"bin$","fromt":"re","to":"md5","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1908,"y":1045,"wires":[["95667d64.83a058"]]},{"id":"95667d64.83a058","type":"fs-ops-move","z":"c8dc8a91.74103","name":"Link MD5","sourcePath":"","sourcePathType":"str","sourceFilename":"payload.linkFile","sourceFilenameType":"msg","destPath":"firmware.directory","destPathType":"flow","destFilename":"payload.linkName","destFilenameType":"msg","link":true,"x":2152.5,"y":1044,"wires":[["eeb8fd90.56a408"]]},{"id":"b598cd93.0da24","type":"change","z":"c8dc8a91.74103","name":"Extract filename","rules":[{"t":"change","p":"payload.filename","pt":"msg","from":"$","fromt":"re","to":".bin","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1382.5,"y":937.5,"wires":[["eea8a4b7.82d8e"]]},{"id":"f6002008.43daa8","type":"fs-ops-link","z":"c8dc8a91.74103","name":"Get Links","path":"firmware.directory","pathType":"flow","filename":"payload.files","filenameType":"msg","link":"payload.links","linkType":"msg","x":776.4999923706055,"y":648.3333110809326,"wires":[["dd6fee12.904a58"]]},{"id":"84851e19.4a5868","type":"debug","z":"c8dc8a91.74103","name":"Update sent","active":true,"console":"false","complete":"req.headers.x-esp8266-sta-mac","x":2931.5000610351562,"y":386.00006103515625,"wires":[]},{"id":"eaeeef87.2b0bf","type":"debug","z":"c8dc8a91.74103","name":"Update req","active":false,"console":"false","complete":"req.headers","x":255.7144012451172,"y":215.71426391601562,"wires":[]},{"id":"196e6dc4.7e4242","type":"switch","z":"c8dc8a91.74103","name":"","property":"req.headers.x-esp8266-version","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"true","outputs":2,"x":696.9642333984375,"y":446.7856750488281,"wires":[["67a99e0a.a70ae8"],["79c3bba9.58bbbc"]]},{"id":"79c3bba9.58bbbc","type":"change","z":"c8dc8a91.74103","name":"Version in Request","rules":[{"t":"set","p":"req.headers.x-esp8266-version","pt":"msg","to":"req.params.version","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":916.4285278320312,"y":512.8571166992188,"wires":[["67a99e0a.a70ae8"]]},{"id":"e718a78e.b5e","type":"debug","z":"c8dc8a91.74103","name":"","active":true,"console":"false","complete":"req.params","x":2126.428741455078,"y":574.2858276367188,"wires":[]},{"id":"7af52504.46f59c","type":"debug","z":"c8dc8a91.74103","name":"Firmware file error","active":true,"console":"false","complete":"error","x":2890.7142944335938,"y":327.1428527832031,"wires":[]},{"id":"56274a30.925974","type":"catch","z":"c8dc8a91.74103","name":"File error","scope":["34f9b5d5.3b5d2a","bf8f43a5.95f42"],"x":2627.8572387695312,"y":328.5714111328125,"wires":[["83413646.c2dd4","7af52504.46f59c"]]}]