mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 17:05:21 +01:00
Add dashboard API to get firmware binaries (#4675)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
f8a03be2f1
commit
c3332e4a39
4 changed files with 94 additions and 25 deletions
|
@ -84,6 +84,23 @@ def get_board(core_obj=None):
|
||||||
return (core_obj or CORE).data[KEY_ESP32][KEY_BOARD]
|
return (core_obj or CORE).data[KEY_ESP32][KEY_BOARD]
|
||||||
|
|
||||||
|
|
||||||
|
def get_download_types(storage_json):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"title": "Modern format",
|
||||||
|
"description": "For use with ESPHome Web and other tools.",
|
||||||
|
"file": "firmware-factory.bin",
|
||||||
|
"download": f"{storage_json.name}-factory.bin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Legacy format",
|
||||||
|
"description": "For use with ESPHome Flasher.",
|
||||||
|
"file": "firmware.bin",
|
||||||
|
"download": f"{storage_json.name}.bin",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def only_on_variant(*, supported=None, unsupported=None):
|
def only_on_variant(*, supported=None, unsupported=None):
|
||||||
"""Config validator for features only available on some ESP32 variants."""
|
"""Config validator for features only available on some ESP32 variants."""
|
||||||
if supported is not None and not isinstance(supported, list):
|
if supported is not None and not isinstance(supported, list):
|
||||||
|
|
|
@ -50,6 +50,17 @@ def set_core_data(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_download_types(storage_json):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"title": "Standard format",
|
||||||
|
"description": "For flashing ESP8266.",
|
||||||
|
"file": "firmware.bin",
|
||||||
|
"download": f"{storage_json.name}.bin",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||||
# format the given arduino (https://github.com/esp8266/Arduino/releases) version to
|
# format the given arduino (https://github.com/esp8266/Arduino/releases) version to
|
||||||
# a PIO platformio/framework-arduinoespressif8266 value
|
# a PIO platformio/framework-arduinoespressif8266 value
|
||||||
|
|
|
@ -42,6 +42,17 @@ def set_core_data(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_download_types(storage_json):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"title": "UF2 format",
|
||||||
|
"description": "For copying to RP2040 over USB.",
|
||||||
|
"file": "firmware.uf2",
|
||||||
|
"download": f"{storage_json.name}.uf2",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||||
# The most recent releases have not been uploaded to platformio so grabbing them directly from
|
# The most recent releases have not been uploaded to platformio so grabbing them directly from
|
||||||
# the GitHub release is one path forward for now.
|
# the GitHub release is one path forward for now.
|
||||||
|
|
|
@ -530,11 +530,43 @@ class ImportRequestHandler(BaseHandler):
|
||||||
self.finish()
|
self.finish()
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadListRequestHandler(BaseHandler):
|
||||||
|
@authenticated
|
||||||
|
@bind_config
|
||||||
|
def get(self, configuration=None):
|
||||||
|
storage_path = ext_storage_path(settings.config_dir, configuration)
|
||||||
|
storage_json = StorageJSON.load(storage_path)
|
||||||
|
if storage_json is None:
|
||||||
|
self.send_error(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
from esphome.components.esp32 import get_download_types as esp32_types
|
||||||
|
from esphome.components.esp8266 import get_download_types as esp8266_types
|
||||||
|
from esphome.components.rp2040 import get_download_types as rp2040_types
|
||||||
|
|
||||||
|
downloads = []
|
||||||
|
platform = storage_json.target_platform.lower()
|
||||||
|
if platform == const.PLATFORM_RP2040:
|
||||||
|
downloads = rp2040_types(storage_json)
|
||||||
|
elif platform == const.PLATFORM_ESP8266:
|
||||||
|
downloads = esp8266_types(storage_json)
|
||||||
|
elif platform == const.PLATFORM_ESP32:
|
||||||
|
downloads = esp32_types(storage_json)
|
||||||
|
else:
|
||||||
|
self.send_error(418)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.set_status(200)
|
||||||
|
self.set_header("content-type", "application/json")
|
||||||
|
self.write(json.dumps(downloads))
|
||||||
|
self.finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class DownloadBinaryRequestHandler(BaseHandler):
|
class DownloadBinaryRequestHandler(BaseHandler):
|
||||||
@authenticated
|
@authenticated
|
||||||
@bind_config
|
@bind_config
|
||||||
def get(self, configuration=None):
|
def get(self, configuration=None):
|
||||||
type = self.get_argument("type", "firmware.bin")
|
|
||||||
compressed = self.get_argument("compressed", "0") == "1"
|
compressed = self.get_argument("compressed", "0") == "1"
|
||||||
|
|
||||||
storage_path = ext_storage_path(settings.config_dir, configuration)
|
storage_path = ext_storage_path(settings.config_dir, configuration)
|
||||||
|
@ -543,27 +575,22 @@ class DownloadBinaryRequestHandler(BaseHandler):
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
return
|
return
|
||||||
|
|
||||||
if storage_json.target_platform.lower() == const.PLATFORM_RP2040:
|
# fallback to type=, but prioritize file=
|
||||||
filename = f"{storage_json.name}.uf2"
|
file_name = self.get_argument("type", None)
|
||||||
path = storage_json.firmware_bin_path.replace(
|
file_name = self.get_argument("file", file_name)
|
||||||
"firmware.bin", "firmware.uf2"
|
if file_name is None:
|
||||||
)
|
self.send_error(400)
|
||||||
|
return
|
||||||
|
file_name = file_name.replace("..", "").lstrip("/")
|
||||||
|
# get requested download name, or build it based on filename
|
||||||
|
download_name = self.get_argument(
|
||||||
|
"download",
|
||||||
|
f"{storage_json.name}-{file_name}",
|
||||||
|
)
|
||||||
|
path = os.path.dirname(storage_json.firmware_bin_path)
|
||||||
|
path = os.path.join(path, file_name)
|
||||||
|
|
||||||
elif storage_json.target_platform.lower() == const.PLATFORM_ESP8266:
|
if not Path(path).is_file():
|
||||||
filename = f"{storage_json.name}.bin"
|
|
||||||
path = storage_json.firmware_bin_path
|
|
||||||
|
|
||||||
elif type == "firmware.bin":
|
|
||||||
filename = f"{storage_json.name}.bin"
|
|
||||||
path = storage_json.firmware_bin_path
|
|
||||||
|
|
||||||
elif type == "firmware-factory.bin":
|
|
||||||
filename = f"{storage_json.name}-factory.bin"
|
|
||||||
path = storage_json.firmware_bin_path.replace(
|
|
||||||
"firmware.bin", "firmware-factory.bin"
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
args = ["esphome", "idedata", settings.rel_path(configuration)]
|
args = ["esphome", "idedata", settings.rel_path(configuration)]
|
||||||
rc, stdout, _ = run_system_command(*args)
|
rc, stdout, _ = run_system_command(*args)
|
||||||
|
|
||||||
|
@ -575,9 +602,9 @@ class DownloadBinaryRequestHandler(BaseHandler):
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
for image in idedata.extra_flash_images:
|
for image in idedata.extra_flash_images:
|
||||||
if image.path.endswith(type):
|
if image.path.endswith(file_name):
|
||||||
path = image.path
|
path = image.path
|
||||||
filename = type
|
download_name = file_name
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -585,10 +612,12 @@ class DownloadBinaryRequestHandler(BaseHandler):
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
return
|
return
|
||||||
|
|
||||||
filename = filename + ".gz" if compressed else filename
|
download_name = download_name + ".gz" if compressed else download_name
|
||||||
|
|
||||||
self.set_header("Content-Type", "application/octet-stream")
|
self.set_header("Content-Type", "application/octet-stream")
|
||||||
self.set_header("Content-Disposition", f'attachment; filename="{filename}"')
|
self.set_header(
|
||||||
|
"Content-Disposition", f'attachment; filename="{download_name}"'
|
||||||
|
)
|
||||||
self.set_header("Cache-Control", "no-cache")
|
self.set_header("Cache-Control", "no-cache")
|
||||||
if not Path(path).is_file():
|
if not Path(path).is_file():
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
|
@ -1259,6 +1288,7 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
||||||
(f"{rel}update-all", EsphomeUpdateAllHandler),
|
(f"{rel}update-all", EsphomeUpdateAllHandler),
|
||||||
(f"{rel}info", InfoRequestHandler),
|
(f"{rel}info", InfoRequestHandler),
|
||||||
(f"{rel}edit", EditRequestHandler),
|
(f"{rel}edit", EditRequestHandler),
|
||||||
|
(f"{rel}downloads", DownloadListRequestHandler),
|
||||||
(f"{rel}download.bin", DownloadBinaryRequestHandler),
|
(f"{rel}download.bin", DownloadBinaryRequestHandler),
|
||||||
(f"{rel}serial-ports", SerialPortRequestHandler),
|
(f"{rel}serial-ports", SerialPortRequestHandler),
|
||||||
(f"{rel}ping", PingRequestHandler),
|
(f"{rel}ping", PingRequestHandler),
|
||||||
|
|
Loading…
Reference in a new issue