Allow downloading all bin files from backend in dashboard (#2514)

Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
Jesse Hills 2021-10-17 19:54:09 +13:00
parent f83950fd75
commit db3fa1ade7
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
2 changed files with 99 additions and 12 deletions

View file

@ -180,7 +180,11 @@ def compile_program(args, config):
from esphome import platformio_api from esphome import platformio_api
_LOGGER.info("Compiling app...") _LOGGER.info("Compiling app...")
return platformio_api.run_compile(config, CORE.verbose) rc = platformio_api.run_compile(config, CORE.verbose)
if rc != 0:
return rc
idedata = platformio_api.get_idedata(config)
return 0 if idedata is not None else 1
def upload_using_esptool(config, port): def upload_using_esptool(config, port):
@ -458,6 +462,21 @@ def command_update_all(args):
return failed return failed
def command_idedata(args, config):
from esphome import platformio_api
import json
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
idedata = platformio_api.get_idedata(config)
if idedata is None:
return 1
print(json.dumps(idedata.raw, indent=2) + "\n")
return 0
PRE_CONFIG_ACTIONS = { PRE_CONFIG_ACTIONS = {
"wizard": command_wizard, "wizard": command_wizard,
"version": command_version, "version": command_version,
@ -475,6 +494,7 @@ POST_CONFIG_ACTIONS = {
"clean-mqtt": command_clean_mqtt, "clean-mqtt": command_clean_mqtt,
"mqtt-fingerprint": command_mqtt_fingerprint, "mqtt-fingerprint": command_mqtt_fingerprint,
"clean": command_clean, "clean": command_clean,
"idedata": command_idedata,
} }
@ -650,6 +670,11 @@ def parse_args(argv):
"configuration", help="Your YAML configuration file directories.", nargs="+" "configuration", help="Your YAML configuration file directories.", nargs="+"
) )
parser_idedata = subparsers.add_parser("idedata")
parser_idedata.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs=1
)
# Keep backward compatibility with the old command line format of # Keep backward compatibility with the old command line format of
# esphome <config> <command>. # esphome <config> <command>.
# #
@ -762,7 +787,7 @@ def run_esphome(argv):
config = read_config(dict(args.substitution) if args.substitution else {}) config = read_config(dict(args.substitution) if args.substitution else {})
if config is None: if config is None:
return 1 return 2
CORE.config = config CORE.config = config
if args.command not in POST_CONFIG_ACTIONS: if args.command not in POST_CONFIG_ACTIONS:

View file

@ -9,6 +9,7 @@ import json
import logging import logging
import multiprocessing import multiprocessing
import os import os
from pathlib import Path
import secrets import secrets
import shutil import shutil
import subprocess import subprocess
@ -26,7 +27,7 @@ import tornado.process
import tornado.web import tornado.web
import tornado.websocket import tornado.websocket
from esphome import const, util from esphome import const, platformio_api, util
from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.helpers import mkdir_p, get_bool_env, run_system_command
from esphome.storage_json import ( from esphome.storage_json import (
EsphomeStorageJSON, EsphomeStorageJSON,
@ -398,17 +399,45 @@ class DownloadBinaryRequestHandler(BaseHandler):
@authenticated @authenticated
@bind_config @bind_config
def get(self, configuration=None): def get(self, configuration=None):
# pylint: disable=no-value-for-parameter type = self.get_argument("type", "firmware.bin")
if type == "firmware.bin":
storage_path = ext_storage_path(settings.config_dir, configuration) storage_path = ext_storage_path(settings.config_dir, configuration)
storage_json = StorageJSON.load(storage_path) storage_json = StorageJSON.load(storage_path)
if storage_json is None: if storage_json is None:
self.send_error() self.send_error(404)
return
filename = f"{storage_json.name}.bin"
path = storage_json.firmware_bin_path
else:
args = ["esphome", "idedata", settings.rel_path(configuration)]
rc, stdout, _ = run_system_command(*args)
if rc != 0:
self.send_error(404 if rc == 2 else 500)
return
idedata = platformio_api.IDEData(json.loads(stdout))
found = False
for image in idedata.extra_flash_images:
if image.path.endswith(type):
path = image.path
filename = type
found = True
break
if not found:
self.send_error(404)
return return
path = storage_json.firmware_bin_path
self.set_header("Content-Type", "application/octet-stream") self.set_header("Content-Type", "application/octet-stream")
filename = f"{storage_json.name}.bin"
self.set_header("Content-Disposition", f'attachment; filename="{filename}"') self.set_header("Content-Disposition", f'attachment; filename="{filename}"')
if not Path(path).is_file():
self.send_error(404)
return
with open(path, "rb") as f: with open(path, "rb") as f:
while True: while True:
data = f.read(16384) data = f.read(16384)
@ -418,6 +447,38 @@ class DownloadBinaryRequestHandler(BaseHandler):
self.finish() self.finish()
class ManifestRequestHandler(BaseHandler):
@authenticated
@bind_config
def get(self, configuration=None):
args = ["esphome", "idedata", settings.rel_path(configuration)]
rc, stdout, _ = run_system_command(*args)
if rc != 0:
self.send_error(404 if rc == 2 else 500)
return
idedata = platformio_api.IDEData(json.loads(stdout))
firmware_offset = "0x10000" if idedata.extra_flash_images else "0x0"
flash_images = [
{
"path": f"./download.bin?configuration={configuration}&type=firmware.bin",
"offset": firmware_offset,
}
] + [
{
"path": f"./download.bin?configuration={configuration}&type={os.path.basename(image.path)}",
"offset": image.offset,
}
for image in idedata.extra_flash_images
]
self.set_header("Content-Type", "application/json")
self.write(json.dumps(flash_images))
self.finish()
def _list_dashboard_entries(): def _list_dashboard_entries():
files = settings.list_yaml_files() files = settings.list_yaml_files()
return [DashboardEntry(file) for file in files] return [DashboardEntry(file) for file in files]
@ -862,6 +923,7 @@ def make_app(debug=get_bool_env(ENV_DEV)):
(f"{rel}info", InfoRequestHandler), (f"{rel}info", InfoRequestHandler),
(f"{rel}edit", EditRequestHandler), (f"{rel}edit", EditRequestHandler),
(f"{rel}download.bin", DownloadBinaryRequestHandler), (f"{rel}download.bin", DownloadBinaryRequestHandler),
(f"{rel}manifest.json", ManifestRequestHandler),
(f"{rel}serial-ports", SerialPortRequestHandler), (f"{rel}serial-ports", SerialPortRequestHandler),
(f"{rel}ping", PingRequestHandler), (f"{rel}ping", PingRequestHandler),
(f"{rel}delete", DeleteRequestHandler), (f"{rel}delete", DeleteRequestHandler),