Skip to content

Instantly share code, notes, and snippets.

@DeanCording
Last active December 4, 2021 07:31
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save DeanCording/888b4cd95250197eb429b2f40d188185 to your computer and use it in GitHub Desktop.
Save DeanCording/888b4cd95250197eb429b2f40d188185 to your computer and use it in GitHub Desktop.
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\">&times;</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'>&times;</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'>&times;</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'>&times;</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"
]
]
}
]
@visibilityspots
Copy link

Hi, I successfully imported your flow in my node-red instance running on a pi3 arch linux distribution. Installed node-red through native npm together with the needed libraries.

When I want to upload a firmware, it's trying to rename the temporarily file to the actual firmware directory and fails to do so:

Error: EXDEV: cross-device link not permitted, rename '/tmp/356ab0a70922b0dd58f2c5558ebb6882' -> '/storage/wemos/firmware/firmware.bin'

After googling around I found out it's because node shouldn't rename files across partitions and should use the mv functionality instead.

http://stackoverflow.com/questions/37153666/error-exdev-cross-device-link-not-permitted-rename-tmp-on-ubuntu-16-04-lts

I'm not sure if this is related to your flow or to the node-red implementation..

@DeanCording
Copy link
Author

Problem is with node-red-contrib-fs-ops. Fixed in 1.2.2

@Rene2705
Copy link

Hi, I am impressed by your article.

I have a node-red instance on my pi2 and I made a 'hello world' version of an esp temperature controller.
The ESP is not easily accessible so this OTA method will come in very helpful.
Looking forward to put this to the test.

I am fairly new to this so I am having a few problems.
Do I really need the ESP8266 2.4.1 core because the documentation of 2.3.0 states that it supports multiple OTA methods. How do i obtain ESP8266 core 2.4.1 ? I can't seem to find it on github. (again, sorry fairly new)

This method doesn't require the server and the ESP to be on the same network, like the arduinoOTA method. correct?
Regards,
Rene

@samiul-hoque
Copy link

Greetings,
I imported your flow, but the web-ui doesnt seem to be loading. I don't see any UI nodes on the flow either, are you sure thefirmware manager can be accessed from http://your.node-red.server:1880/esp8266-ota ?

@DeanCording
Copy link
Author

DeanCording commented Nov 14, 2020 via email

@samiul-hoque
Copy link

samiul-hoque commented Nov 15, 2020

You need to change 'your.node-red.sever' to the name of your actual Node Red server. On Friday, 13 November 2020 21:44:56 AEST Samiul Hoque wrote: @samiul-hoque commented on this gist. -------------------- Greetings,I imported your flow, but the web-ui doesnt seem to be loading. I don't see any UI nodes on the flow either, are you sure thefirmware manager can be accessed from http://your.node-red.server:1880/esp8266-ota[1] ? —You are receiving this because you authored the thread.Reply to this email directly, view it on GitHub[2], or unsubscribe[3].[4]

-------- [1] http://your.node-red.server:1880/esp8266-ota [2] https://gist.github.com/888b4cd95250197eb429b2f40d188185#gistcomment-3526270 [3] https://github.com/notifications/unsubscribe-auth/ AAB5KISIEAML53QVDXQ5JJ3SPUL3RANCNFSM4TUPOBXQ [4] https://github.com/notifications/beacon/ AAB5KIWBIS5C2Z6DTB5Y6DDSPUL3RA5CNFSM4TUPOBX2YY3PNVWWK3TUL52HS4DFVND WS43UINXW23LFNZ2KUY3PNVWWK3TUL5UWJTQAGXHH4.gif

Yes obviously, I replaced it with my server IP. I am still am not being able to load it. Here's the message payload from the ota-manager http get node,

Error: ENOENT: no such file or directory, scandir '/home/sysadmin/firmware/'

EDIT:
Solved it, by editing the 'Firmware Config' node. Firmware directory changed from '/home/sysadmin/firmware/' to /home//firmware/

@undercurrent77
Copy link

undercurrent77 commented Feb 3, 2021

This is amazing work. Thanks so much for sharing this! Just had a quick question I hope you can help with. I've got it all up and running, but when I upload a new firmware .bin file to the web server, my esp reports through the serial monitor that the update fails.

Checking for firmware updates from server http://---.---.-.---:1880/esp8266-ota/update
HTTP update failed: Error (-102): File Not Found (404)

Am I correct to compile the binary out of the arduino ide and upload that file? Or should I be doing something else? I see reference to SPIFFS in the update type node on your node-red flow so was just curious.

Thanks!

@WittmannJ
Copy link

WittmannJ commented Jul 4, 2021

Very impressive code. However I do not find the node-red package needed for the "config"-node aka the node named "Firmware config". Would you mind pointing me to the correct module? Thank you very much!

EDIT: Nevermind I found it myself :), here is the link for anyone who is curious Link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment