From 8632c77d68e4129effef60b1ad8d839995f9134b Mon Sep 17 00:00:00 2001 From: quantum-x Date: Thu, 30 Jun 2022 16:47:08 +0200 Subject: [PATCH 1/2] 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 b44f77cb8..9dbfd9d03 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 0be24b512..78094cddb 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 2/2] 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 | 114 +++++++++----------------- scripts/storage.py | 89 ++++++++++---------- 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, 312 insertions(+), 263 deletions(-) create mode 100644 scripts/flipper/utils/cdc.py diff --git a/ReadMe.md b/ReadMe.md index 6a3eed377..e8b4daabc 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 312b23a64..7e8fc8962 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 e70208ab9..d5465534d 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 061339da4..9658ff6a2 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 81193a468..c95b1371b 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 7711e44b5..67d42132a 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 ddeff0481..3cad7e58c 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -46,7 +46,7 @@ OPENOCD_OPTS = '-f interface/stlink.cfg -c "transport select hla_swd" -f debug/s SVD_FILE = "debug/STM32WB55_CM4.svd" FIRMWARE_APPS = { - "default": ( + "default": [ "crypto_start", # Svc "basic_services", @@ -62,11 +62,11 @@ FIRMWARE_APPS = { "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 890be5f87..4c9988ba8 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 a75a8a9e8..958356021 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 000000000..081705cc2 --- /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 538ecdb98..a22ca1f59 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 @@ class Main: 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 @@ class Main: 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 + 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] + 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}" + 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 + 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 + 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() + 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 1281253bc..0ddc2fc0c 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 @@ class Main: ) self.parser_stress.set_defaults(func=self.stress) - # logging - self.logger = logging.getLogger() + def _get_storage(self): + if not (port := resolve_port(self.logger, self.args.port)): + return None - 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() + storage = FlipperStorage(port, self.args.baud) + storage.start() + return storage def mkdir(self): - storage = FlipperStorage(self.args.port) - storage.start() + 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 @@ class Main: 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 @@ class Main: 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 @@ class Main: 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 @@ class Main: 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 @@ class Main: 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 @@ class Main: 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 9de24a9aa..11509b2d1 100644 --- a/site_scons/fbt/util.py +++ b/site_scons/fbt/util.py @@ -2,7 +2,9 @@ import SCons 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 cbeae2d0a..bdccccf7b 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 8bfb40684..fcfecbcbf 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 e7b6bdd68..94ea9fbe9 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 135e1100a..dcf0bf925 100644 --- a/site_scons/site_tools/openocd.py +++ b/site_scons/site_tools/openocd.py @@ -7,7 +7,7 @@ __OPENOCD_BIN = "openocd" _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 eeb900229..a4bb9f65a 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):