mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 08:55:22 +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]
|
||||
|
||||
|
||||
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):
|
||||
"""Config validator for features only available on some ESP32 variants."""
|
||||
if supported is not None and not isinstance(supported, list):
|
||||
|
|
|
@ -50,6 +50,17 @@ def set_core_data(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:
|
||||
# format the given arduino (https://github.com/esp8266/Arduino/releases) version to
|
||||
# a PIO platformio/framework-arduinoespressif8266 value
|
||||
|
|
|
@ -42,6 +42,17 @@ def set_core_data(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:
|
||||
# The most recent releases have not been uploaded to platformio so grabbing them directly from
|
||||
# the GitHub release is one path forward for now.
|
||||
|
|
|
@ -530,11 +530,43 @@ class ImportRequestHandler(BaseHandler):
|
|||
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):
|
||||
@authenticated
|
||||
@bind_config
|
||||
def get(self, configuration=None):
|
||||
type = self.get_argument("type", "firmware.bin")
|
||||
compressed = self.get_argument("compressed", "0") == "1"
|
||||
|
||||
storage_path = ext_storage_path(settings.config_dir, configuration)
|
||||
|
@ -543,27 +575,22 @@ class DownloadBinaryRequestHandler(BaseHandler):
|
|||
self.send_error(404)
|
||||
return
|
||||
|
||||
if storage_json.target_platform.lower() == const.PLATFORM_RP2040:
|
||||
filename = f"{storage_json.name}.uf2"
|
||||
path = storage_json.firmware_bin_path.replace(
|
||||
"firmware.bin", "firmware.uf2"
|
||||
)
|
||||
# fallback to type=, but prioritize file=
|
||||
file_name = self.get_argument("type", None)
|
||||
file_name = self.get_argument("file", file_name)
|
||||
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:
|
||||
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:
|
||||
if not Path(path).is_file():
|
||||
args = ["esphome", "idedata", settings.rel_path(configuration)]
|
||||
rc, stdout, _ = run_system_command(*args)
|
||||
|
||||
|
@ -575,9 +602,9 @@ class DownloadBinaryRequestHandler(BaseHandler):
|
|||
|
||||
found = False
|
||||
for image in idedata.extra_flash_images:
|
||||
if image.path.endswith(type):
|
||||
if image.path.endswith(file_name):
|
||||
path = image.path
|
||||
filename = type
|
||||
download_name = file_name
|
||||
found = True
|
||||
break
|
||||
|
||||
|
@ -585,10 +612,12 @@ class DownloadBinaryRequestHandler(BaseHandler):
|
|||
self.send_error(404)
|
||||
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-Disposition", f'attachment; filename="{filename}"')
|
||||
self.set_header(
|
||||
"Content-Disposition", f'attachment; filename="{download_name}"'
|
||||
)
|
||||
self.set_header("Cache-Control", "no-cache")
|
||||
if not Path(path).is_file():
|
||||
self.send_error(404)
|
||||
|
@ -1259,6 +1288,7 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
|||
(f"{rel}update-all", EsphomeUpdateAllHandler),
|
||||
(f"{rel}info", InfoRequestHandler),
|
||||
(f"{rel}edit", EditRequestHandler),
|
||||
(f"{rel}downloads", DownloadListRequestHandler),
|
||||
(f"{rel}download.bin", DownloadBinaryRequestHandler),
|
||||
(f"{rel}serial-ports", SerialPortRequestHandler),
|
||||
(f"{rel}ping", PingRequestHandler),
|
||||
|
|
Loading…
Reference in a new issue