From e6f18cc32272bd4e23ac9eee8241c6e916b3b9ad Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 28 Jun 2022 15:28:55 +0300 Subject: [PATCH 1/4] fbt: building `core` with respect for debug flag (#1347) * fbt: building `core` with respect for debug flag * fbt: added size output for firmware elf * Infrared: fix cli Co-authored-by: Aleksandr Kutuzov --- applications/infrared/infrared_cli.c | 1 + firmware.scons | 13 ++++++++++++- site_scons/site_tools/crosscc.py | 4 +++- site_scons/site_tools/fbt_dist.py | 3 +-- site_scons/site_tools/size.py | 24 ++++++++++++++++++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 site_scons/site_tools/size.py diff --git a/applications/infrared/infrared_cli.c b/applications/infrared/infrared_cli.c index 1b3ec4b3167..d88e7fff51a 100644 --- a/applications/infrared/infrared_cli.c +++ b/applications/infrared/infrared_cli.c @@ -97,6 +97,7 @@ static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) return false; } + message.protocol = infrared_get_protocol_by_name(protocol_name); message.repeat = false; infrared_signal_set_message(signal, &message); return infrared_signal_is_valid(signal); diff --git a/firmware.scons b/firmware.scons index 3652c298e1d..890be5f8742 100644 --- a/firmware.scons +++ b/firmware.scons @@ -39,7 +39,17 @@ env = ENV.Clone( ], # You can add other entries named after libraries # If they are present, they have precedence over Default - } + }, + # for furi_check to respect build type + "core": { + "CCFLAGS": [ + "-Os", + ], + "CPPDEFINES": [ + "NDEBUG", + "FURI_DEBUG" if ENV["DEBUG"] else "FURI_NDEBUG", + ], + }, }, ) @@ -191,6 +201,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( # Make it depend on everything child builders returned Depends(fwelf, lib_targets) AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) +AddPostAction(fwelf, Action("@$SIZECOM")) fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") diff --git a/site_scons/site_tools/crosscc.py b/site_scons/site_tools/crosscc.py index 0770df716ae..8d6b4a61879 100644 --- a/site_scons/site_tools/crosscc.py +++ b/site_scons/site_tools/crosscc.py @@ -6,6 +6,7 @@ import strip import gdb import objdump +import size from SCons.Action import _subproc import subprocess @@ -36,7 +37,7 @@ def _get_tool_version(env, tool): def generate(env, **kw): - for orig_tool in (asm, gcc, gxx, ar, gnulink, strip, gdb, objdump): + for orig_tool in (asm, gcc, gxx, ar, gnulink, strip, gdb, objdump, size): orig_tool.generate(env) env.SetDefault( TOOLCHAIN_PREFIX=kw.get("toolchain_prefix"), @@ -55,6 +56,7 @@ def generate(env, **kw): "GDB", "GDBPY", "OBJDUMP", + "SIZE", ], ) # Call CC to check version diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index c39f724259f..8fad2156cf1 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -77,8 +77,7 @@ def generate(env): BUILDERS={ "DistBuilder": Builder( action=Action( - '${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', - "${DISTCOMSTR}", + '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', ), ), "CoproBuilder": Builder( diff --git a/site_scons/site_tools/size.py b/site_scons/site_tools/size.py new file mode 100644 index 00000000000..56d4f3c9ef0 --- /dev/null +++ b/site_scons/site_tools/size.py @@ -0,0 +1,24 @@ +from SCons.Builder import Builder +from SCons.Action import Action + + +def generate(env): + env.SetDefault( + SIZE="size", + SIZEFLAGS=[], + SIZECOM="$SIZE $SIZEFLAGS $TARGETS", + ) + env.Append( + BUILDERS={ + "ELFSize": Builder( + action=Action( + "${SIZECOM}", + "${SIZECOMSTR}", + ), + ), + } + ) + + +def exists(env): + return True From 8b988e2b17ec512505f7c9362794e82ff49fe17d Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 28 Jun 2022 16:03:49 +0300 Subject: [PATCH 2/4] fbt: updater over USB (#1344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Scripts: added update package uploader over USB; fbt: added flash_usb target * fbt: additional dependencies for flash_usb * Cli: fix cursor_position corruption Co-authored-by: あく --- SConstruct | 13 ++- applications/cli/cli.c | 3 +- documentation/fbt.md | 4 +- scripts/flipper/storage.py | 4 +- scripts/selfupdate.py | 143 ++++++++++++++++++++++++++++++ scripts/storage.py | 24 +++-- site_scons/site_tools/fbt_dist.py | 10 +++ 7 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 scripts/selfupdate.py diff --git a/SConstruct b/SConstruct index a31811aef04..312b23a6470 100644 --- a/SConstruct +++ b/SConstruct @@ -84,6 +84,16 @@ if GetOption("fullenv"): debug_updater_elf = distenv.AddDebugTarget(updater_out, False) Alias("updater_debug", debug_updater_elf) + # Installation over USB & CLI + usb_update_package = distenv.UsbInstall( + "usbinstall.flag", + (distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"], selfupdate_dist), + ) + if distenv["FORCE"]: + AlwaysBuild(usb_update_package) + Depends(usb_update_package, selfupdate_dist) + Alias("flash_usb", usb_update_package) + # Target for copying & renaming binaries to dist folder basic_dist = distenv.DistBuilder("dist.pseudo", distenv["DIST_DEPENDS"]) @@ -92,6 +102,7 @@ AlwaysBuild(basic_dist) Alias("fw_dist", basic_dist) Default(basic_dist) + # Target for bundling core2 package for qFlipper copro_dist = distenv.CoproBuilder( Dir("assets/core2_firmware"), @@ -113,7 +124,7 @@ debug_other = distenv.GDBPy( None, GDBPYOPTS= # '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" ' - '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ' + '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', ) distenv.Pseudo("debugother.pseudo") AlwaysBuild(debug_other) diff --git a/applications/cli/cli.c b/applications/cli/cli.c index eefbb305225..aa48e93bfc3 100644 --- a/applications/cli/cli.c +++ b/applications/cli/cli.c @@ -149,7 +149,8 @@ void cli_reset(Cli* cli) { } static void cli_handle_backspace(Cli* cli) { - if(string_size(cli->line) > 0) { + if(cli->cursor_position > 0) { + furi_assert(string_size(cli->line) > 0); // Other side printf("\e[D\e[1P"); fflush(stdout); diff --git a/documentation/fbt.md b/documentation/fbt.md index 7ada4bea267..061339da4e3 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -28,6 +28,8 @@ FBT keeps track of internal dependencies, so you only need to build the highest- - `fw_dist` - build & publish firmware to `dist` folder - `updater_package` - build self-update package. _Requires `--with-updater` option_ - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper +- `flash` - flash attached device with OpenOCD over ST-Link +- `flash_usb` - build, upload and install update package to device over USB. _Requires `--with-updater` option_ - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded - `debug_updater` - attach gdb with updater's .elf loaded. _Requires `--with-updater` option_ - `debug_other` - attach gdb without loading built elf. Allows to manually add external elf files with `add-symbol-file` in gdb. @@ -39,7 +41,7 @@ FBT keeps track of internal dependencies, so you only need to build the highest- - `firmware_snake_game`, etc - build single plug-in as .elf by its name - Check out `--extra-ext-apps` for force adding extra apps to external build - `firmware_snake_game_list`, etc - generate source + assembler listing for app's .elf -- `firmware_flash` - flash current version to attached device with OpenOCD +- `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link - `firmware_cdb` - generate compilation database - `firmware_all`, `updater_all` - build basic set of binaries - `firmware_list`, `updater_list` - generate source + assembler listing diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index e15377d79ff..3d1b46b9483 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -53,11 +53,11 @@ class FlipperStorage: CLI_PROMPT = ">: " CLI_EOL = "\r\n" - def __init__(self, portname: str): + def __init__(self, portname: str, portbaud: int = 115200): self.port = serial.Serial() self.port.port = portname self.port.timeout = 2 - self.port.baudrate = 115200 + self.port.baudrate = portbaud self.read = BufferedRead(self.port) self.last_error = "" diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py new file mode 100644 index 00000000000..538ecdb98b1 --- /dev/null +++ b/scripts/selfupdate.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 + +from flipper.storage import FlipperStorage + +import logging +import argparse +import os +import sys +import pathlib +import serial.tools.list_ports as list_ports + + +class Main: + def __init__(self): + # command args + self.parser = argparse.ArgumentParser() + self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") + self.parser.add_argument( + "-b", + "--baud", + help="Port Baud rate", + required=False, + default=115200 * 4, + type=int, + ) + + self.subparsers = self.parser.add_subparsers(help="sub-command help") + + self.parser_install = self.subparsers.add_parser( + "install", help="Install OTA package" + ) + self.parser_install.add_argument("manifest_path", help="Manifest path") + self.parser_install.add_argument( + "--pkg_dir_name", help="Update dir name", default="pcbundle", required=False + ) + self.parser_install.set_defaults(func=self.install) + + # logging + self.logger = logging.getLogger() + + def __call__(self): + self.args = self.parser.parse_args() + if "func" not in self.args: + self.parser.error("Choose something to do") + # configure log output + self.log_level = logging.DEBUG if self.args.debug else logging.INFO + self.logger.setLevel(self.log_level) + self.handler = logging.StreamHandler(sys.stdout) + self.handler.setLevel(self.log_level) + self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") + self.handler.setFormatter(self.formatter) + self.logger.addHandler(self.handler) + # execute requested function + self.args.func() + + # make directory with exist check + def mkdir_on_storage(self, storage, flipper_dir_path): + if not storage.exist_dir(flipper_dir_path): + self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') + if not storage.mkdir(flipper_dir_path): + self.logger.error(f"Error: {storage.last_error}") + return False + else: + self.logger.debug(f'"{flipper_dir_path}" already exists') + return True + + # send file with exist check and hash check + def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): + exists = storage.exist_file(flipper_file_path) + do_upload = not exists + if exists: + hash_local = storage.hash_local(local_file_path) + hash_flipper = storage.hash_flipper(flipper_file_path) + self.logger.debug(f"hash check: local {hash_local}, flipper {hash_flipper}") + do_upload = force or (hash_local != hash_flipper) + + if do_upload: + self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') + if not storage.send_file(local_file_path, flipper_file_path): + self.logger.error(f"Error: {storage.last_error}") + return False + return True + + def _get_port(self): + if self.args.port != "auto": + return self.args.port + # Try guessing + flippers = list(list_ports.grep("flip")) + if len(flippers) == 1: + flipper = flippers[0] + self.logger.info(f"Using {flipper.serial_number} on {flipper.device}") + return flipper.device + elif len(flippers) == 0: + self.logger.error("Failed to find connected Flipper") + elif len(flippers) > 1: + self.logger.error("More than one Flipper is attached") + self.logger.error("Failed to guess which port to use. Specify --port") + + def install(self): + if not (port := self._get_port()): + return 1 + + storage = FlipperStorage(port, self.args.baud) + storage.start() + + if not os.path.isfile(self.args.manifest_path): + self.logger.error("Error: manifest not found") + return 2 + + manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) + manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] + + pkg_dir_name = self.args.pkg_dir_name or pkg_name + flipper_update_path = f"/ext/update/{pkg_dir_name}" + + self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') + # if not os.path.exists(self.args.manifest_path): + # self.logger.error("Error: package not found") + if not self.mkdir_on_storage(storage, flipper_update_path): + self.logger.error(f"Error: cannot create {storage.last_error}") + return -2 + + for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): + for fname in filenames: + self.logger.debug(f"Uploading {fname}") + local_file_path = os.path.join(dirpath, fname) + flipper_file_path = f"{flipper_update_path}/{fname}" + if not self.send_file_to_storage( + storage, flipper_file_path, local_file_path, False + ): + self.logger.error(f"Error: {storage.last_error}") + return -3 + + storage.send_and_wait_eol( + f"update install {flipper_update_path}/{manifest_name}\r" + ) + break + storage.stop() + + +if __name__ == "__main__": + Main()() diff --git a/scripts/storage.py b/scripts/storage.py index 4364eb28a2b..1281253bc43 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -18,6 +18,14 @@ def __init__(self): self.parser = argparse.ArgumentParser() self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") self.parser.add_argument("-p", "--port", help="CDC Port", required=True) + self.parser.add_argument( + "-b", + "--baud", + help="Port Baud rate", + required=False, + default=115200 * 4, + type=int, + ) self.subparsers = self.parser.add_subparsers(help="sub-command help") self.parser_mkdir = self.subparsers.add_parser("mkdir", help="Create directory") @@ -195,31 +203,31 @@ def send_to_storage(self, storage, flipper_path, local_path, force): # make directory with exist check def mkdir_on_storage(self, storage, flipper_dir_path): if not storage.exist_dir(flipper_dir_path): - self.logger.debug(f'"{flipper_dir_path}" not exist, creating') + self.logger.debug(f'"{flipper_dir_path}" does not exist, creating') if not storage.mkdir(flipper_dir_path): self.logger.error(f"Error: {storage.last_error}") else: - self.logger.debug(f'"{flipper_dir_path}" already exist') + self.logger.debug(f'"{flipper_dir_path}" already exists') # send file with exist check and hash check def send_file_to_storage(self, storage, flipper_file_path, local_file_path, force): if not storage.exist_file(flipper_file_path): self.logger.debug( - f'"{flipper_file_path}" not exist, sending "{local_file_path}"' + f'"{flipper_file_path}" does not exist, sending "{local_file_path}"' ) self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') if not storage.send_file(local_file_path, flipper_file_path): self.logger.error(f"Error: {storage.last_error}") elif force: self.logger.debug( - f'"{flipper_file_path}" exist, but will be overwritten by "{local_file_path}"' + f'"{flipper_file_path}" exists, but will be overwritten by "{local_file_path}"' ) self.logger.info(f'Sending "{local_file_path}" to "{flipper_file_path}"') if not storage.send_file(local_file_path, flipper_file_path): self.logger.error(f"Error: {storage.last_error}") else: self.logger.debug( - f'"{flipper_file_path}" exist, compare hash with "{local_file_path}"' + f'"{flipper_file_path}" exists, compare hash with "{local_file_path}"' ) hash_local = storage.hash_local(local_file_path) hash_flipper = storage.hash_flipper(flipper_file_path) @@ -229,11 +237,11 @@ def send_file_to_storage(self, storage, flipper_file_path, local_file_path, forc if hash_local == hash_flipper: self.logger.debug( - f'"{flipper_file_path}" are equal to "{local_file_path}"' + f'"{flipper_file_path}" is equal to "{local_file_path}"' ) else: self.logger.debug( - f'"{flipper_file_path}" are not equal to "{local_file_path}"' + f'"{flipper_file_path}" is NOT equal to "{local_file_path}"' ) self.logger.info( f'Sending "{local_file_path}" to "{flipper_file_path}"' @@ -242,7 +250,7 @@ def send_file_to_storage(self, storage, flipper_file_path, local_file_path, forc self.logger.error(f"Error: {storage.last_error}") def read(self): - storage = FlipperStorage(self.args.port) + storage = FlipperStorage(self.args.port, self.args.baud) storage.start() self.logger.debug(f'Reading "{self.args.flipper_path}"') data = storage.read_file(self.args.flipper_path) diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index 8fad2156cf1..8bfb40684ca 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -1,6 +1,7 @@ from SCons.Builder import Builder from SCons.Action import Action from SCons.Script import Mkdir +from SCons.Defaults import Touch def get_variant_dirname(env, project=None): @@ -47,6 +48,7 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): project_env["FW_ARTIFACTS"], ], ) + env.Replace(DIST_DIR=get_variant_dirname(env)) return project_env @@ -80,6 +82,14 @@ def generate(env): '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', ), ), + "UsbInstall": Builder( + action=[ + Action( + "${PYTHON3} ${ROOT_DIR.abspath}/scripts/selfupdate.py install dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf" + ), + Touch("${TARGET}"), + ] + ), "CoproBuilder": Builder( action=Action( [ From 8632c77d68e4129effef60b1ad8d839995f9134b Mon Sep 17 00:00:00 2001 From: quantum-x Date: Thu, 30 Jun 2022 16:47:08 +0200 Subject: [PATCH 3/4] 1342 add mifare infineon (#1346) * Adding MIFARE 1K Infineon Compatibility As per Issue #1342, MIFARE Classic 1K Cards from NXP have the SAK value of 0x08. MIFARE Classic 1K Cards from Infineon have an SAK value of 0x88. Adding the SAK values accordingly so that Infineon tags are properly handled. --- lib/nfc_protocols/mifare_classic.c | 4 ++-- lib/nfc_protocols/mifare_common.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index b44f77cb8d8..9dbfd9d0390 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -198,7 +198,7 @@ static bool mf_classic_is_allowed_access( bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) { return true; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { return true; @@ -219,7 +219,7 @@ bool mf_classic_get_type( furi_assert(reader); memset(reader, 0, sizeof(MfClassicReader)); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) { reader->type = MfClassicType1k; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { reader->type = MfClassicType4k; diff --git a/lib/nfc_protocols/mifare_common.c b/lib/nfc_protocols/mifare_common.c index 0be24b512c0..78094cddb3e 100644 --- a/lib/nfc_protocols/mifare_common.c +++ b/lib/nfc_protocols/mifare_common.c @@ -6,7 +6,7 @@ MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { type = MifareTypeUltralight; } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) || + ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) || ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18))) { type = MifareTypeClassic; } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { From b3767d143f47cfbb28194545ba13458c2680e301 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 30 Jun 2022 19:06:12 +0300 Subject: [PATCH 4/4] fbt: fixes (#1352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: added --git-tasks; fixed typos * fbt: fixed --extra-int-apps handling; scripts: moved storage.py & selfupdate.py to App() framework * fbt: changed pseudo-builders to PhonyTargets with commands; added link to latest build dir as build/latest * fbt: Restored old ep git handling * fbt: dropped git tasks & dirlink.py * fbt: removed extra quoting in fbt.cmd * docs: added flash_usb to ReadMe.md Co-authored-by: あく --- ReadMe.md | 8 +- SConstruct | 88 +++++++----------- assets/SConscript | 48 +++++----- documentation/fbt.md | 11 +-- fbt | 14 ++- fbt.cmd | 9 +- fbt_options.py | 8 +- firmware.scons | 62 ++++++++++--- scripts/flipper/app.py | 1 - scripts/flipper/utils/cdc.py | 17 ++++ scripts/selfupdate.py | 124 ++++++++++---------------- scripts/storage.py | 91 ++++++++++--------- site_scons/fbt/util.py | 27 +++++- site_scons/site_tools/fbt_apps.py | 5 +- site_scons/site_tools/fbt_dist.py | 37 +++++--- site_scons/site_tools/gdb.py | 16 ---- site_scons/site_tools/openocd.py | 10 +-- site_scons/site_tools/sconsmodular.py | 11 +++ 18 files changed, 318 insertions(+), 269 deletions(-) create mode 100644 scripts/flipper/utils/cdc.py diff --git a/ReadMe.md b/ReadMe.md index 6a3eed377ff..e8b4daabcef 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -27,9 +27,15 @@ They both must be flashed in order described. ## With offline update package +With Flipper attached over USB: + +`./fbt --with-updater flash_usb` + +Just building the package: + `./fbt --with-updater updater_package` -Copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app. +To update, copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app. ## With STLink diff --git a/SConstruct b/SConstruct index 312b23a6470..7e8fc896223 100644 --- a/SConstruct +++ b/SConstruct @@ -17,7 +17,6 @@ fbt_variables = SConscript("site_scons/commandline.scons") cmd_environment = Environment(tools=[], variables=fbt_variables) Help(fbt_variables.GenerateHelpText(cmd_environment)) - # Building basic environment - tools, utility methods, cross-compilation # settings, gcc flags for Cortex-M4, basic builders and more coreenv = SConscript( @@ -43,7 +42,6 @@ firmware_out = distenv.AddFwProject( fw_env_key="FW_ENV", ) - # If enabled, initialize updater-related targets if GetOption("fullenv"): updater_out = distenv.AddFwProject( @@ -71,91 +69,67 @@ if GetOption("fullenv"): "--splash", distenv.subst("assets/slideshow/$UPDATE_SPLASH"), ] - selfupdate_dist = distenv.DistBuilder( - "selfupdate.pseudo", + + selfupdate_dist = distenv.DistCommand( + "updater_package", (distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"]), DIST_EXTRA=dist_arguments, ) - distenv.Pseudo("selfupdate.pseudo") - AlwaysBuild(selfupdate_dist) - Alias("updater_package", selfupdate_dist) # Updater debug - debug_updater_elf = distenv.AddDebugTarget(updater_out, False) - Alias("updater_debug", debug_updater_elf) + distenv.AddDebugTarget("updater_debug", updater_out, False) # Installation over USB & CLI usb_update_package = distenv.UsbInstall( - "usbinstall.flag", - (distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"], selfupdate_dist), + "#build/usbinstall.flag", + ( + distenv["DIST_DEPENDS"], + firmware_out["FW_RESOURCES"], + selfupdate_dist, + ), ) if distenv["FORCE"]: - AlwaysBuild(usb_update_package) - Depends(usb_update_package, selfupdate_dist) - Alias("flash_usb", usb_update_package) - + distenv.AlwaysBuild(usb_update_package) + distenv.Depends(usb_update_package, selfupdate_dist) + distenv.Alias("flash_usb", usb_update_package) # Target for copying & renaming binaries to dist folder -basic_dist = distenv.DistBuilder("dist.pseudo", distenv["DIST_DEPENDS"]) -distenv.Pseudo("dist.pseudo") -AlwaysBuild(basic_dist) -Alias("fw_dist", basic_dist) -Default(basic_dist) - +basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) +distenv.Default(basic_dist) # Target for bundling core2 package for qFlipper copro_dist = distenv.CoproBuilder( - Dir("assets/core2_firmware"), + distenv.Dir("assets/core2_firmware"), [], ) -AlwaysBuild(copro_dist) -Alias("copro_dist", copro_dist) - +distenv.Alias("copro_dist", copro_dist) # Debugging firmware - -debug_fw_elf = distenv.AddDebugTarget(firmware_out) -Alias("debug", debug_fw_elf) - - +distenv.AddDebugTarget("debug", firmware_out) # Debug alien elf -debug_other = distenv.GDBPy( - "debugother.pseudo", - None, +distenv.PhonyTarget( + "debug_other", + "$GDBPYCOM", GDBPYOPTS= # '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" ' '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', ) -distenv.Pseudo("debugother.pseudo") -AlwaysBuild(debug_other) -Alias("debug_other", debug_other) - # Just start OpenOCD -openocd = distenv.OOCDCommand("openocd.pseudo", []) -distenv.Pseudo("openocd.pseudo") -AlwaysBuild(openocd) -Alias("openocd", openocd) - +distenv.PhonyTarget( + "openocd", + "${OPENOCDCOM}", +) # Linter -lint_check = distenv.Command( - "lint.check.pseudo", - [], - "${PYTHON3} scripts/lint.py check $LINT_SOURCES", +distenv.PhonyTarget( + "lint", + "${PYTHON3} scripts/lint.py check ${LINT_SOURCES}", LINT_SOURCES=firmware_out["LINT_SOURCES"], ) -distenv.Pseudo("lint.check.pseudo") -AlwaysBuild(lint_check) -Alias("lint", lint_check) - -lint_format = distenv.Command( - "lint.format.pseudo", - [], - "${PYTHON3} scripts/lint.py format $LINT_SOURCES", +distenv.PhonyTarget( + "format", + "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}", LINT_SOURCES=firmware_out["LINT_SOURCES"], ) -distenv.Pseudo("lint.format.pseudo") -AlwaysBuild(lint_format) -Alias("format", lint_format) diff --git a/assets/SConscript b/assets/SConscript index e70208ab919..d5465534df2 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -28,38 +28,38 @@ icons_src = assetsenv.GlobRecursive("*.png", "icons") icons_src += assetsenv.GlobRecursive("frame_rate", "icons") icons = assetsenv.IconBuilder(Dir("compiled"), Dir("#/assets/icons")) -Depends(icons, icons_src) -Alias("icons", icons) +assetsenv.Depends(icons, icons_src) +assetsenv.Alias("icons", icons) # Protobuf .proto -> .c + .h -proto_src = Glob("protobuf/*.proto", source=True) -proto_options = Glob("protobuf/*.options", source=True) -proto = assetsenv.ProtoBuilder(Dir("compiled"), proto_src) -Depends(proto, proto_options) +proto_src = assetsenv.Glob("protobuf/*.proto", source=True) +proto_options = assetsenv.Glob("protobuf/*.options", source=True) +proto = assetsenv.ProtoBuilder(assetsenv.Dir("compiled"), proto_src) +assetsenv.Depends(proto, proto_options) # Precious(proto) -Alias("proto", proto) +assetsenv.Alias("proto", proto) # Internal animations dolphin_internal = assetsenv.DolphinSymBuilder( - Dir("compiled"), - Dir("#/assets/dolphin"), + assetsenv.Dir("compiled"), + assetsenv.Dir("#/assets/dolphin"), DOLPHIN_RES_TYPE="internal", ) -Alias("dolphin_internal", dolphin_internal) +assetsenv.Alias("dolphin_internal", dolphin_internal) # Blocking animations dolphin_blocking = assetsenv.DolphinSymBuilder( - Dir("compiled"), - Dir("#/assets/dolphin"), + assetsenv.Dir("compiled"), + assetsenv.Dir("#/assets/dolphin"), DOLPHIN_RES_TYPE="blocking", ) -Alias("dolphin_blocking", dolphin_blocking) +assetsenv.Alias("dolphin_blocking", dolphin_blocking) # Protobuf version meta @@ -67,8 +67,8 @@ proto_ver = assetsenv.ProtoVerBuilder( "compiled/protobuf_version.h", "#/assets/protobuf/Changelog", ) -Depends(proto_ver, proto) -Alias("proto_ver", proto_ver) +assetsenv.Depends(proto_ver, proto) +assetsenv.Alias("proto_ver", proto_ver) # Gather everything into a static lib assets_parts = (icons, proto, dolphin_blocking, dolphin_internal, proto_ver) @@ -82,14 +82,14 @@ assetsenv.Install("${LIB_DIST_DIR}", assetslib) if assetsenv["IS_BASE_FIRMWARE"]: # External dolphin animations dolphin_external = assetsenv.DolphinExtBuilder( - Dir("#/assets/resources/dolphin"), - Dir("#/assets/dolphin"), + assetsenv.Dir("#/assets/resources/dolphin"), + assetsenv.Dir("#/assets/dolphin"), DOLPHIN_RES_TYPE="external", ) - NoClean(dolphin_external) + assetsenv.NoClean(dolphin_external) if assetsenv["FORCE"]: - AlwaysBuild(dolphin_external) - Alias("dolphin_ext", dolphin_external) + assetsenv.AlwaysBuild(dolphin_external) + assetsenv.Alias("dolphin_ext", dolphin_external) # Resources manifest @@ -101,13 +101,13 @@ if assetsenv["IS_BASE_FIRMWARE"]: "${RESMANIFESTCOMSTR}", ), ) - Precious(resources) - NoClean(resources) + assetsenv.Precious(resources) + assetsenv.NoClean(resources) if assetsenv["FORCE"]: - AlwaysBuild(resources) + assetsenv.AlwaysBuild(resources) # Exporting resources node to external environment env["FW_RESOURCES"] = resources - Alias("resources", resources) + assetsenv.Alias("resources", resources) Return("assetslib") diff --git a/documentation/fbt.md b/documentation/fbt.md index 061339da4e3..9658ff6a229 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -1,11 +1,12 @@ # Flipper Build Tool -FBT is the entry point for most firmware-related commands and utilities. +FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. ## Requirements Please install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` +Make sure that `gcc-arm-none-eabi` toolchain & OpenOCD executables are in system's PATH. ## NB @@ -17,22 +18,22 @@ To build with FBT, call it specifying configuration options & targets to build. `./fbt --with-updater COMPACT=1 DEBUG=0 VERBOSE=1 updater_package copro_dist` -To run cleanup (think of `make clean`) for specified targets, all `-c` option. +To run cleanup (think of `make clean`) for specified targets, add `-c` option. ## FBT targets -FBT keeps track of internal dependencies, so you only need to build the highest-level target you need, and FBT will make sure everything it needs is up-to-date. +FBT keeps track of internal dependencies, so you only need to build the highest-level target you need, and FBT will make sure everything they depend on is up-to-date. ### High-level (what you most likely need) -- `fw_dist` - build & publish firmware to `dist` folder +- `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified - `updater_package` - build self-update package. _Requires `--with-updater` option_ - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `flash` - flash attached device with OpenOCD over ST-Link - `flash_usb` - build, upload and install update package to device over USB. _Requires `--with-updater` option_ - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded - `debug_updater` - attach gdb with updater's .elf loaded. _Requires `--with-updater` option_ -- `debug_other` - attach gdb without loading built elf. Allows to manually add external elf files with `add-symbol-file` in gdb. +- `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb. - `openocd` - just start OpenOCD ### Firmware targets diff --git a/fbt b/fbt index 81193a46897..c95b1371b81 100755 --- a/fbt +++ b/fbt @@ -2,11 +2,17 @@ set -e +SCRIPTDIR="$( dirname -- "$0"; )"; +SCONS_EP=${SCRIPTDIR}/lib/scons/scripts/scons.py + if [[ -d .git ]]; then - echo "Updating git submodules" - git submodule update --init + echo Updating git submodules + git submodule update --init +else # Not in a git repo + echo Not in a git repo, please clone with git clone --recursive + # Return error code 1 to indicate failure + exit 1 fi -SCRIPTDIR="$( dirname -- "$0"; )"; SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built" -python3 ${SCRIPTDIR}/lib/scons/scripts/scons.py ${SCONS_DEFAULT_FLAGS} "$@" +python3 ${SCONS_EP} ${SCONS_DEFAULT_FLAGS} "$@" diff --git a/fbt.cmd b/fbt.cmd index 7711e44b594..67d42132ab3 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -1,8 +1,11 @@ @echo off + +set SCONS_EP=%~dp0\lib\scons\scripts\scons.py + if exist ".git" ( - echo Prepairing git submodules - git submodule update --init + echo Updating git submodules + git submodule update --init ) set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" -python lib/scons/scripts/scons.py %SCONS_DEFAULT_FLAGS% %* +python %SCONS_EP% %SCONS_DEFAULT_FLAGS% %* diff --git a/fbt_options.py b/fbt_options.py index ddeff048144..3cad7e58ce3 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -46,7 +46,7 @@ SVD_FILE = "debug/STM32WB55_CM4.svd" FIRMWARE_APPS = { - "default": ( + "default": [ "crypto_start", # Svc "basic_services", @@ -62,11 +62,11 @@ "basic_plugins", # Debug "debug_apps", - ), - "unit_tests": ( + ], + "unit_tests": [ "basic_services", "unit_tests", - ), + ], } FIRMWARE_APP_SET = "default" diff --git a/firmware.scons b/firmware.scons index 890be5f8742..4c9988ba83c 100644 --- a/firmware.scons +++ b/firmware.scons @@ -2,6 +2,7 @@ Import("ENV", "fw_build_meta") import os +from fbt.util import link_dir # Building initial C environment for libs env = ENV.Clone( @@ -72,7 +73,7 @@ if not env["VERBOSE"]: HEXCOMSTR="\tHEX\t${TARGET}", BINCOMSTR="\tBIN\t${TARGET}", DFUCOMSTR="\tDFU\t${TARGET}", - OOCDCOMSTR="\tFLASH\t${SOURCE}", + OPENOCDCOMSTR="\tFLASH\t${SOURCE}", ) @@ -116,8 +117,7 @@ else: fwenv.Append(APPS=["updater"]) if extra_int_apps := GetOption("extra_int_apps"): - for extra_int_app in extra_int_apps.split(","): - fwenv.Append(APPS=[extra_int_app]) + fwenv.Append(APPS=extra_int_apps.split(",")) fwenv.LoadApplicationManifests() fwenv.PrepareApplicationsBuild() @@ -198,41 +198,83 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( ], ) + +def link_elf_dir_as_latest(env, elf_target): + # Ugly way to check if updater-related targets were requested + elf_dir = elf_target.Dir(".") + explicitly_building_updater = False + # print("BUILD_TARGETS:", ','.join(BUILD_TARGETS)) + for build_target in BUILD_TARGETS: + # print(">>> ", str(build_target)) + if "updater" in str(build_target): + explicitly_building_updater = True + + latest_dir = env.Dir("#build/latest") + + link_this_dir = True + if explicitly_building_updater: + # If updater is explicitly requested, link to the latest updater + # Otherwise, link to the latest firmware + link_this_dir = not env["IS_BASE_FIRMWARE"] + + if link_this_dir: + print(f"Setting {elf_dir} as latest built dir") + return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") + + +def link_latest_dir(env, target, source): + return link_elf_dir_as_latest(env, target[0]) + + # Make it depend on everything child builders returned Depends(fwelf, lib_targets) AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction(fwelf, Action("@$SIZECOM")) +AddPostAction(fwelf, Action(link_latest_dir, None)) + +link_dir_command = fwenv["LINK_DIR_CMD"] = fwenv.PhonyTarget( + "${FIRMWARE_BUILD_CFG}" + "_latest", + Action(lambda target, source, env: link_elf_dir_as_latest(env, source[0]), None), + source=fwelf, +) fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}") fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}") -# Default(dfu) Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_dfu", fwdfu) fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}") Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump) # Additional FW-related pseudotargets -flash = fwenv["FW_FLASH"] = fwenv.OOCDFlashCommand( +flash = fwenv["FW_FLASH"] = fwenv.OpenOCDFlash( + "#build/oocd-${FIRMWARE_BUILD_CFG}-flash.flag", "${FIRMWARE_BUILD_CFG}", OPENOCD_COMMAND='-c "program ${SOURCE.posix} reset exit ${IMAGE_BASE_ADDRESS}"', ) if fwenv["FORCE"]: - AlwaysBuild(flash) -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_flash", flash) + fwenv.AlwaysBuild(flash) +fwenv.Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_flash", flash) if fwenv["IS_BASE_FIRMWARE"]: - Alias("flash", flash) + fwenv.Alias("flash", flash) # Compile DB generation fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase("compile_commands.json") -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) +fwenv.Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) -artifacts = [fwhex, fwbin, fwdfu, env["FW_VERSION_JSON"]] +artifacts = [ + fwhex, + fwbin, + fwdfu, + env["FW_VERSION_JSON"], + fwcdb, +] fwenv["FW_ARTIFACTS"] = artifacts Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", artifacts) + Return("fwenv") diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index a75a8a9e84d..9583560212c 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -1,7 +1,6 @@ import logging import argparse import sys -import os class App: diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py new file mode 100644 index 00000000000..081705cc257 --- /dev/null +++ b/scripts/flipper/utils/cdc.py @@ -0,0 +1,17 @@ +import serial.tools.list_ports as list_ports + +# Returns a valid port or None, if it cannot be found +def resolve_port(logger, portname: str = "auto"): + if portname != "auto": + return portname + # Try guessing + flippers = list(list_ports.grep("flip")) + if len(flippers) == 1: + flipper = flippers[0] + logger.info(f"Using {flipper.serial_number} on {flipper.device}") + return flipper.device + elif len(flippers) == 0: + logger.error("Failed to find connected Flipper") + elif len(flippers) > 1: + logger.error("More than one Flipper is attached") + logger.error("Failed to guess which port to use. Specify --port") diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index 538ecdb98b1..a22ca1f5929 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -1,20 +1,18 @@ #!/usr/bin/env python3 +from typing import final +from flipper.app import App from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port import logging -import argparse import os -import sys import pathlib import serial.tools.list_ports as list_ports -class Main: - def __init__(self): - # command args - self.parser = argparse.ArgumentParser() - self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") +class Main(App): + def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( "-b", @@ -25,35 +23,15 @@ def __init__(self): type=int, ) - self.subparsers = self.parser.add_subparsers(help="sub-command help") - - self.parser_install = self.subparsers.add_parser( - "install", help="Install OTA package" - ) - self.parser_install.add_argument("manifest_path", help="Manifest path") - self.parser_install.add_argument( + self.parser.add_argument("manifest_path", help="Manifest path") + self.parser.add_argument( "--pkg_dir_name", help="Update dir name", default="pcbundle", required=False ) - self.parser_install.set_defaults(func=self.install) + self.parser.set_defaults(func=self.install) # logging self.logger = logging.getLogger() - def __call__(self): - self.args = self.parser.parse_args() - if "func" not in self.args: - self.parser.error("Choose something to do") - # configure log output - self.log_level = logging.DEBUG if self.args.debug else logging.INFO - self.logger.setLevel(self.log_level) - self.handler = logging.StreamHandler(sys.stdout) - self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") - self.handler.setFormatter(self.formatter) - self.logger.addHandler(self.handler) - # execute requested function - self.args.func() - # make directory with exist check def mkdir_on_storage(self, storage, flipper_dir_path): if not storage.exist_dir(flipper_dir_path): @@ -82,61 +60,49 @@ def send_file_to_storage(self, storage, flipper_file_path, local_file_path, forc return False return True - def _get_port(self): - if self.args.port != "auto": - return self.args.port - # Try guessing - flippers = list(list_ports.grep("flip")) - if len(flippers) == 1: - flipper = flippers[0] - self.logger.info(f"Using {flipper.serial_number} on {flipper.device}") - return flipper.device - elif len(flippers) == 0: - self.logger.error("Failed to find connected Flipper") - elif len(flippers) > 1: - self.logger.error("More than one Flipper is attached") - self.logger.error("Failed to guess which port to use. Specify --port") - def install(self): - if not (port := self._get_port()): + if not (port := resolve_port(self.logger, self.args.port)): return 1 storage = FlipperStorage(port, self.args.baud) storage.start() - if not os.path.isfile(self.args.manifest_path): - self.logger.error("Error: manifest not found") - return 2 - - manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) - manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] - - pkg_dir_name = self.args.pkg_dir_name or pkg_name - flipper_update_path = f"/ext/update/{pkg_dir_name}" - - self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') - # if not os.path.exists(self.args.manifest_path): - # self.logger.error("Error: package not found") - if not self.mkdir_on_storage(storage, flipper_update_path): - self.logger.error(f"Error: cannot create {storage.last_error}") - return -2 - - for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): - for fname in filenames: - self.logger.debug(f"Uploading {fname}") - local_file_path = os.path.join(dirpath, fname) - flipper_file_path = f"{flipper_update_path}/{fname}" - if not self.send_file_to_storage( - storage, flipper_file_path, local_file_path, False - ): - self.logger.error(f"Error: {storage.last_error}") - return -3 - - storage.send_and_wait_eol( - f"update install {flipper_update_path}/{manifest_name}\r" - ) - break - storage.stop() + try: + if not os.path.isfile(self.args.manifest_path): + self.logger.error("Error: manifest not found") + return 2 + + manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) + manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] + + pkg_dir_name = self.args.pkg_dir_name or pkg_name + flipper_update_path = f"/ext/update/{pkg_dir_name}" + + self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') + # if not os.path.exists(self.args.manifest_path): + # self.logger.error("Error: package not found") + if not self.mkdir_on_storage(storage, flipper_update_path): + self.logger.error(f"Error: cannot create {storage.last_error}") + return -2 + + for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): + for fname in filenames: + self.logger.debug(f"Uploading {fname}") + local_file_path = os.path.join(dirpath, fname) + flipper_file_path = f"{flipper_update_path}/{fname}" + if not self.send_file_to_storage( + storage, flipper_file_path, local_file_path, False + ): + self.logger.error(f"Error: {storage.last_error}") + return -3 + + storage.send_and_wait_eol( + f"update install {flipper_update_path}/{manifest_name}\r" + ) + break + return 0 + finally: + storage.stop() if __name__ == "__main__": diff --git a/scripts/storage.py b/scripts/storage.py index 1281253bc43..0ddc2fc0c99 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -1,23 +1,19 @@ #!/usr/bin/env python3 +from flipper.app import App from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port import logging -import argparse import os -import sys import binascii -import posixpath import filecmp import tempfile -class Main: - def __init__(self): - # command args - self.parser = argparse.ArgumentParser() - self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") - self.parser.add_argument("-p", "--port", help="CDC Port", required=True) +class Main(App): + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( "-b", "--baud", @@ -77,43 +73,37 @@ def __init__(self): ) self.parser_stress.set_defaults(func=self.stress) - # logging - self.logger = logging.getLogger() - - def __call__(self): - self.args = self.parser.parse_args() - if "func" not in self.args: - self.parser.error("Choose something to do") - # configure log output - self.log_level = logging.DEBUG if self.args.debug else logging.INFO - self.logger.setLevel(self.log_level) - self.handler = logging.StreamHandler(sys.stdout) - self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") - self.handler.setFormatter(self.formatter) - self.logger.addHandler(self.handler) - # execute requested function - self.args.func() + def _get_storage(self): + if not (port := resolve_port(self.logger, self.args.port)): + return None - def mkdir(self): - storage = FlipperStorage(self.args.port) + storage = FlipperStorage(port, self.args.baud) storage.start() + return storage + + def mkdir(self): + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Creating "{self.args.flipper_path}"') if not storage.mkdir(self.args.flipper_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def remove(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Removing "{self.args.flipper_path}"') if not storage.remove(self.args.flipper_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def receive(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 if storage.exist_dir(self.args.flipper_path): for dirpath, dirnames, filenames in storage.walk(self.args.flipper_path): @@ -155,14 +145,17 @@ def receive(self): if not storage.receive_file(self.args.flipper_path, self.args.local_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def send(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.send_to_storage( storage, self.args.flipper_path, self.args.local_path, self.args.force ) storage.stop() + return 0 # send file or folder recursively def send_to_storage(self, storage, flipper_path, local_path, force): @@ -250,8 +243,9 @@ def send_file_to_storage(self, storage, flipper_file_path, local_file_path, forc self.logger.error(f"Error: {storage.last_error}") def read(self): - storage = FlipperStorage(self.args.port, self.args.baud) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Reading "{self.args.flipper_path}"') data = storage.read_file(self.args.flipper_path) if not data: @@ -264,10 +258,12 @@ def read(self): print("Binary hexadecimal data:") print(binascii.hexlify(data).decode()) storage.stop() + return 0 def size(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Getting size of "{self.args.flipper_path}"') size = storage.size(self.args.flipper_path) if size < 0: @@ -275,13 +271,16 @@ def size(self): else: print(size) storage.stop() + return 0 def list(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Listing "{self.args.flipper_path}"') storage.list_tree(self.args.flipper_path) storage.stop() + return 0 def stress(self): self.logger.error("This test is wearing out flash memory.") @@ -293,18 +292,21 @@ def stress(self): self.logger.error("Stop at this point or device warranty will be void") say = input("Anything to say? ").strip().lower() if say != "void": - return + return 2 say = input("Why, Mr. Anderson? ").strip().lower() if say != "because": - return + return 3 with tempfile.TemporaryDirectory() as tmpdirname: send_file_name = os.path.join(tmpdirname, "send") receive_file_name = os.path.join(tmpdirname, "receive") with open(send_file_name, "w") as fout: fout.write("A" * self.args.file_size) - storage = FlipperStorage(self.args.port) - storage.start() + + storage = self._get_storage() + if not storage: + return 1 + if storage.exist_file(self.args.flipper_path): self.logger.error("File exists, remove it first") return @@ -318,6 +320,7 @@ def stress(self): os.unlink(receive_file_name) self.args.count -= 1 storage.stop() + return 0 if __name__ == "__main__": diff --git a/site_scons/fbt/util.py b/site_scons/fbt/util.py index 9de24a9aa52..11509b2d189 100644 --- a/site_scons/fbt/util.py +++ b/site_scons/fbt/util.py @@ -2,7 +2,9 @@ from SCons.Subst import quote_spaces import re - +import os +import random +import string WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") @@ -17,3 +19,26 @@ def tempfile_arg_esc_func(arg): def wrap_tempfile(env, command): env[command] = '${TEMPFILE("' + env[command] + '","$' + command + 'STR")}' + + +def link_dir(target_path, source_path, is_windows): + # print(f"link_dir: {target_path} -> {source_path}") + if os.path.lexists(target_path) or os.path.exists(target_path): + os.unlink(target_path) + if is_windows: + # Crete junction + import _winapi + + if not os.path.isdir(source_path): + raise Exception(f"Source directory {source_path} is not a directory") + + if not os.path.exists(target_path): + _winapi.CreateJunction(source_path, target_path) + else: + os.symlink(source_path, target_path) + + +def random_alnum(length): + return "".join( + random.choice(string.ascii_letters + string.digits) for _ in range(length) + ) diff --git a/site_scons/site_tools/fbt_apps.py b/site_scons/site_tools/fbt_apps.py index cbeae2d0a62..bdccccf7b5c 100644 --- a/site_scons/site_tools/fbt_apps.py +++ b/site_scons/site_tools/fbt_apps.py @@ -51,7 +51,10 @@ def generate(env): env.Append( BUILDERS={ "ApplicationsC": Builder( - action=Action(build_apps_c, "${APPSCOMSTR}"), + action=Action( + build_apps_c, + "${APPSCOMSTR}", + ), ), } ) diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index 8bfb40684ca..fcfecbcbf29 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -46,17 +46,19 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): ], DIST_DEPENDS=[ project_env["FW_ARTIFACTS"], + project_env["LINK_DIR_CMD"], ], ) + env.Replace(DIST_DIR=get_variant_dirname(env)) return project_env -def AddDebugTarget(env, targetenv, force_flash=True): - pseudo_name = f"debug.{targetenv.subst('$FIRMWARE_BUILD_CFG')}.pseudo" - debug_target = env.GDBPy( - pseudo_name, - targetenv["FW_ELF"], +def AddDebugTarget(env, alias, targetenv, force_flash=True): + debug_target = env.PhonyTarget( + alias, + "$GDBPYCOM", + source=targetenv["FW_ELF"], GDBPYOPTS='-ex "source debug/FreeRTOS/FreeRTOS.py" ' '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ' '-ex "svd_load ${SVD_FILE}" ' @@ -64,28 +66,37 @@ def AddDebugTarget(env, targetenv, force_flash=True): ) if force_flash: env.Depends(debug_target, targetenv["FW_FLASH"]) - env.Pseudo(pseudo_name) - env.AlwaysBuild(debug_target) + return debug_target +def DistCommand(env, name, source, **kw): + target = f"dist_{name}" + command = env.Command( + target, + source, + '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', + **kw, + ) + env.Pseudo(target) + env.Alias(name, command) + return command + + def generate(env): env.AddMethod(AddFwProject) env.AddMethod(AddDebugTarget) + env.AddMethod(DistCommand) env.SetDefault( COPRO_MCU_FAMILY="STM32WB5x", ) + env.Append( BUILDERS={ - "DistBuilder": Builder( - action=Action( - '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', - ), - ), "UsbInstall": Builder( action=[ Action( - "${PYTHON3} ${ROOT_DIR.abspath}/scripts/selfupdate.py install dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf" + "${PYTHON3} ${ROOT_DIR.abspath}/scripts/selfupdate.py dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf" ), Touch("${TARGET}"), ] diff --git a/site_scons/site_tools/gdb.py b/site_scons/site_tools/gdb.py index e7b6bdd6875..94ea9fbe9be 100644 --- a/site_scons/site_tools/gdb.py +++ b/site_scons/site_tools/gdb.py @@ -11,22 +11,6 @@ def generate(env): GDBCOM="$GDB $GDBOPTS $SOURCES", # no $TARGET GDBPYCOM="$GDBPY $GDBOPTS $GDBPYOPTS $SOURCES", # no $TARGET ) - env.Append( - BUILDERS={ - "GDB": Builder( - action=Action( - "${GDBCOM}", - "${GDBCOMSTR}", - ), - ), - "GDBPy": Builder( - action=Action( - "${GDBPYCOM}", - "${GDBPYCOMSTR}", - ), - ), - } - ) def exists(env): diff --git a/site_scons/site_tools/openocd.py b/site_scons/site_tools/openocd.py index 135e1100a9e..dcf0bf925aa 100644 --- a/site_scons/site_tools/openocd.py +++ b/site_scons/site_tools/openocd.py @@ -7,7 +7,7 @@ _oocd_action = Action( "${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", - "${OOCDCOMSTR}", + "${OPENOCDCOMSTR}", ) @@ -16,12 +16,13 @@ def generate(env): OPENOCD=__OPENOCD_BIN, OPENOCD_OPTS="", OPENOCD_COMMAND="", - OOCDCOMSTR="", + OPENOCDCOM="${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", + OPENOCDCOMSTR="", ) env.Append( BUILDERS={ - "OOCDFlashCommand": Builder( + "OpenOCDFlash": Builder( action=[ _oocd_action, Touch("${TARGET}"), @@ -29,9 +30,6 @@ def generate(env): suffix=".flash", src_suffix=".bin", ), - "OOCDCommand": Builder( - action=_oocd_action, - ), } ) diff --git a/site_scons/site_tools/sconsmodular.py b/site_scons/site_tools/sconsmodular.py index eeb900229c9..a4bb9f65af6 100644 --- a/site_scons/site_tools/sconsmodular.py +++ b/site_scons/site_tools/sconsmodular.py @@ -29,9 +29,20 @@ def BuildModules(env, modules): return result +def PhonyTarget(env, name, action, source=None, **kw): + if not source: + source = [] + phony_name = "phony_" + name + env.Pseudo(phony_name) + return env.AlwaysBuild( + env.Alias(name, env.Command(phony_name, source, action, **kw)) + ) + + def generate(env): env.AddMethod(BuildModule) env.AddMethod(BuildModules) + env.AddMethod(PhonyTarget) def exists(env):