[FL-1443, FL-1289] Move assets compilation to separate Makefile. Add scripts folder. Add OTP flashing with DFU. (#531)

* Assets: move assets compilation to separate Makefile. Move all scripts to scripts folder. Add scripts ReadMe. Add precompiled assets.
* Split assets.py into separate entities. Option bytes for FL-1289 and checker/setter.
* Cli: explicitly initialize variable befor use in api_hal_vcp_rx_with_timeout
* Rename ob_check script to ob.
This commit is contained in:
あく
2021-06-23 17:58:44 +03:00
committed by GitHub
parent 359bbdfe69
commit 8116bfcbab
23 changed files with 1110 additions and 87 deletions

51
scripts/ReadMe.md Normal file
View File

@@ -0,0 +1,51 @@
# About
This folder contains differnt scripts that automates routine actions.
Flashing scripts are based on cli version of [STM32CubeProgrammer](https://www.st.com/en/development-tools/stm32cubeprog.html).
You will need to add STM32_Programmer_CLI to your path to use them.
# Flashing empty MCU/Flipper
Always flash your device in the folllowing sequence:
- OTP (Only on empty MCU)
- Core2 firmware
- Core1 firmware
- Option Bytes
## Otp flashing
!!! Flashing incorrect OTP may permanently brick your device !!!
Normally OTP data generated and flashed at factory.
In case if MCU was replaced you'll need correct OTP data to be able to use companion applications.
Use `otp.py` to generate OTP data and `flash_otp_version_*` to flash OTP zone.
You will need exact main board revision to genrate OTP data. It can be found on main PCB.
!!! Flashing incorrect OTP may permanently brick your device !!!
## Core2 flashing
Script blindly updates FUS and Radiostack. This operation is going to corrupt bootloader and firmware.
Reflash Core1 after Core2.
## Core1 flashing
Script compiles and flashes both bootloader and firmware.
## Option Bytes
!!! Setting incorrect Otion Bytes may brick your MCU !!!
Defaults are mostly OK, but there are couple things that we'd like to tune.
Also OB may be damaged, so we've made couple scripts to check and set option bytes.
!!! Setting incorrect Otion Bytes may brick your MCU !!!
Checking option bytes:
ob.py check
Setting option bytes:
ob.py set

199
scripts/assets.py Executable file
View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python3
import logging
import argparse
import subprocess
import io
import os
import sys
ICONS_SUPPORTED_FORMATS = ["png"]
ICONS_TEMPLATE_H_HEADER = """#pragma once
#include <gui/icon.h>
typedef enum {
"""
ICONS_TEMPLATE_H_ICON_NAME = "\t{name},\n"
ICONS_TEMPLATE_H_FOOTER = """} IconName;
Icon * assets_icons_get(IconName name);
"""
ICONS_TEMPLATE_H_I = """#pragma once
#include <assets_icons.h>
const IconData * assets_icons_get_data(IconName name);
"""
ICONS_TEMPLATE_C_HEADER = """#include \"assets_icons_i.h\"
#include <gui/icon_i.h>
"""
ICONS_TEMPLATE_C_FRAME = "const uint8_t {name}[] = {data};\n"
ICONS_TEMPLATE_C_DATA = "const uint8_t *{name}[] = {data};\n"
ICONS_TEMPLATE_C_ICONS_ARRAY_START = "const IconData icons[] = {\n"
ICONS_TEMPLATE_C_ICONS_ITEM = "\t{{ .width={width}, .height={height}, .frame_count={frame_count}, .frame_rate={frame_rate}, .frames=_{name} }},\n"
ICONS_TEMPLATE_C_ICONS_ARRAY_END = "};"
ICONS_TEMPLATE_C_FOOTER = """
const IconData * assets_icons_get_data(IconName name) {
return &icons[name];
}
Icon * assets_icons_get(IconName name) {
return icon_alloc(assets_icons_get_data(name));
}
"""
class Main:
def __init__(self):
# command args
self.parser = argparse.ArgumentParser()
self.parser.add_argument("-d", "--debug", action="store_true", help="Debug")
self.subparsers = self.parser.add_subparsers(help="sub-command help")
self.parser_icons = self.subparsers.add_parser(
"icons", help="Process icons and build icon registry"
)
self.parser_icons.add_argument(
"-s", "--source-directory", help="Source directory"
)
self.parser_icons.add_argument(
"-o", "--output-directory", help="Output directory"
)
self.parser_icons.set_defaults(func=self.icons)
# 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 icons(self):
self.logger.debug(f"Converting icons")
icons_c = open(os.path.join(self.args.output_directory, "assets_icons.c"), "w")
icons_c.write(ICONS_TEMPLATE_C_HEADER)
icons = []
# Traverse icons tree, append image data to source file
for dirpath, dirnames, filenames in os.walk(self.args.source_directory):
self.logger.debug(f"Processing directory {dirpath}")
if not filenames:
continue
if "frame_rate" in filenames:
self.logger.debug(f"Folder contatins animation")
icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_")
width = height = None
frame_count = 0
frame_rate = 0
frame_names = []
for filename in sorted(filenames):
fullfilename = os.path.join(dirpath, filename)
if filename == "frame_rate":
frame_rate = int(open(fullfilename, "r").read().strip())
continue
elif not self.iconIsSupported(filename):
continue
self.logger.debug(f"Processing animation frame {filename}")
temp_width, temp_height, data = self.icon2header(fullfilename)
if width is None:
width = temp_width
if height is None:
height = temp_height
assert width == temp_width
assert height == temp_height
frame_name = f"_{icon_name}_{frame_count}"
frame_names.append(frame_name)
icons_c.write(
ICONS_TEMPLATE_C_FRAME.format(name=frame_name, data=data)
)
frame_count += 1
assert frame_rate > 0
assert frame_count > 0
icons_c.write(
ICONS_TEMPLATE_C_DATA.format(
name=f"_{icon_name}", data=f'{{{",".join(frame_names)}}}'
)
)
icons_c.write("\n")
icons.append((icon_name, width, height, frame_rate, frame_count))
else:
# process icons
for filename in filenames:
if not self.iconIsSupported(filename):
continue
self.logger.debug(f"Processing icon {filename}")
icon_name = "I_" + "_".join(filename.split(".")[:-1]).replace(
"-", "_"
)
fullfilename = os.path.join(dirpath, filename)
width, height, data = self.icon2header(fullfilename)
frame_name = f"_{icon_name}_0"
icons_c.write(
ICONS_TEMPLATE_C_FRAME.format(name=frame_name, data=data)
)
icons_c.write(
ICONS_TEMPLATE_C_DATA.format(
name=f"_{icon_name}", data=f"{{{frame_name}}}"
)
)
icons_c.write("\n")
icons.append((icon_name, width, height, 0, 1))
# Create array of images:
self.logger.debug(f"Finalizing source file")
icons_c.write(ICONS_TEMPLATE_C_ICONS_ARRAY_START)
for name, width, height, frame_rate, frame_count in icons:
icons_c.write(
ICONS_TEMPLATE_C_ICONS_ITEM.format(
name=name,
width=width,
height=height,
frame_rate=frame_rate,
frame_count=frame_count,
)
)
icons_c.write(ICONS_TEMPLATE_C_ICONS_ARRAY_END)
icons_c.write(ICONS_TEMPLATE_C_FOOTER)
icons_c.write("\n")
# Create Public Header
self.logger.debug(f"Creating header")
icons_h = open(os.path.join(self.args.output_directory, "assets_icons.h"), "w")
icons_h.write(ICONS_TEMPLATE_H_HEADER)
for name, width, height, frame_rate, frame_count in icons:
icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name))
icons_h.write(ICONS_TEMPLATE_H_FOOTER)
# Create Private Header
icons_h_i = open(
os.path.join(self.args.output_directory, "assets_icons_i.h"), "w"
)
icons_h_i.write(ICONS_TEMPLATE_H_I)
self.logger.debug(f"Done")
def icon2header(self, file):
output = subprocess.check_output(["convert", file, "xbm:-"])
assert output
f = io.StringIO(output.decode().strip())
width = int(f.readline().strip().split(" ")[2])
height = int(f.readline().strip().split(" ")[2])
data = f.read().strip().replace("\n", "").replace(" ", "").split("=")[1][:-1]
return width, height, data
def iconIsSupported(self, filename):
extension = filename.lower().split(".")[-1]
return extension in ICONS_SUPPORTED_FORMATS
if __name__ == "__main__":
Main()()

12
scripts/flash_core1_main_swd.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -x -e
SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
rm $PROJECT_DIR/bootloader/.obj/f*/flash || true
make -C $PROJECT_DIR/bootloader -j9 flash
rm $PROJECT_DIR/firmware/.obj/f*/flash || true
make -C $PROJECT_DIR/firmware -j9 flash

20
scripts/flash_core2_ble_swd.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -x -e
SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
COPRO_DIR="$PROJECT_DIR/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x"
STM32_Programmer_CLI -c port=swd -fwupgrade $COPRO_DIR/stm32wb5x_FUS_fw_1_0_2.bin 0x080EC000 || true
STM32_Programmer_CLI -c port=swd
STM32_Programmer_CLI -c port=swd -fwupgrade $COPRO_DIR/stm32wb5x_FUS_fw.bin 0x080EC000 || true
STM32_Programmer_CLI -c port=swd
STM32_Programmer_CLI -c port=swd -fwdelete
STM32_Programmer_CLI -c port=swd -fwupgrade $COPRO_DIR/stm32wb5x_BLE_Stack_full_fw.bin 0x080CB000 firstinstall=0
STM32_Programmer_CLI -c port=swd -ob nSWBOOT0=1 nBOOT0=1

View File

@@ -0,0 +1,17 @@
#!/bin/bash
set -x -e
if [ "$#" -ne 1 ]; then
echo "OTP file required"
exit
fi
if [ ! -f $1 ]; then
echo "Unable to open OTP file"
exit
fi
STM32_Programmer_CLI -c port=usb1 -d $1 0x1FFF7000
STM32_Programmer_CLI -c port=usb1 -r8 0x1FFF7000 8

View File

@@ -0,0 +1,17 @@
#!/bin/bash
set -x -e
if [ "$#" -ne 1 ]; then
echo "OTP file required"
exit
fi
if [ ! -f $1 ]; then
echo "Unable to open OTP file"
exit
fi
STM32_Programmer_CLI -c port=swd -d $1 0x1FFF7000
STM32_Programmer_CLI -c port=swd -r8 0x1FFF7000 8

11
scripts/flash_wipe_swd.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -x -e
STM32_Programmer_CLI -c port=swd -ob RDP=0xBB
STM32_Programmer_CLI -c port=swd -ob displ
STM32_Programmer_CLI -c port=swd --readunprotect
STM32_Programmer_CLI -c port=swd -ob displ

113
scripts/ob.py Executable file
View File

@@ -0,0 +1,113 @@
#!/usr/bin/env python3
import logging
import argparse
import subprocess
import sys
import os
class Main:
def __init__(self):
# command args
self.parser = argparse.ArgumentParser()
self.parser.add_argument("-d", "--debug", action="store_true", help="Debug")
self.subparsers = self.parser.add_subparsers(help="sub-command help")
self.parser_check = self.subparsers.add_parser(
"check", help="Check Option Bytes"
)
self.parser_check.set_defaults(func=self.check)
self.parser_set = self.subparsers.add_parser("set", help="Set Option Bytes")
self.parser_set.set_defaults(func=self.set)
# logging
self.logger = logging.getLogger()
# OB
self.ob = {}
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.loadOB()
self.args.func()
def loadOB(self):
self.logger.info(f"Loading Option Bytes data")
file_path = os.path.join(os.path.dirname(sys.argv[0]), "ob.data")
file = open(file_path, "r")
for line in file.readlines():
k, v, o = line.split(":")
self.ob[k.strip()] = v.strip(), o.strip()
def check(self):
self.logger.info(f"Checking Option Bytes")
try:
output = subprocess.check_output(
["STM32_Programmer_CLI", "-q", "-c port=swd", "-ob displ"]
)
assert output
except Exception as e:
self.logger.error(f"Failed to call STM32_Programmer_CLI")
self.logger.exception(e)
return
ob_correct = True
for line in output.decode().split("\n"):
line = line.strip()
if not ":" in line:
self.logger.debug(f"Skipping line: {line}")
continue
key, data = line.split(":", 1)
key = key.strip()
data = data.strip()
if not key in self.ob.keys():
self.logger.debug(f"Skipping key: {key}")
continue
self.logger.debug(f"Processing key: {key} {data}")
value, comment = data.split(" ", 1)
value = value.strip()
comment = comment.strip()
if self.ob[key][0] != value:
self.logger.error(
f"Invalid OB: {key} {value}, expected: {self.ob[key][0]}"
)
ob_correct = False
if ob_correct:
self.logger.info(f"OB Check OK")
else:
self.logger.error(f"OB Check FAIL")
exit(255)
def set(self):
self.logger.info(f"Setting Option Bytes")
options = []
for key, (value, attr) in self.ob.items():
if "w" in attr:
options.append(f"{key}={value}")
try:
output = subprocess.check_output(
[
"STM32_Programmer_CLI",
"-q",
"-c port=swd",
f"-ob {' '.join(options)}",
]
)
assert output
self.logger.info(f"Success")
except Exception as e:
self.logger.error(f"Failed to call STM32_Programmer_CLI")
self.logger.exception(e)
return
if __name__ == "__main__":
Main()()

34
scripts/ob_check.data Normal file
View File

@@ -0,0 +1,34 @@
RDP:0xAA:r
BOR_LEV:0x4:rw
nBOOT0:0x1:rw
nBOOT1:0x1:rw
nSWBOOT0:0x1:rw
SRAM2RST:0x0:rw
SRAM2PE:0x1:rw
nRST_STOP:0x1:rw
nRST_STDBY:0x1:rw
nRSTSHDW:0x1:rw
WWDGSW:0x1:rw
IWGDSTDBY:0x1:rw
IWDGSTOP:0x1:rw
IWDGSW:0x1:rw
IPCCDBA:0x0:rw
ESE:0x1:r
SFSA:0xCB:r
FSD:0x0:r
DDS:0x1:r
C2OPT:0x1:r
NBRSD:0x0:r
SNBRSA:0xF:r
BRSD:0x0:r
SBRSA:0xA:r
SBRV:0x32C00:r
PCROP1A_STRT:0x1FF:r
PCROP1A_END:0x0:r
PCROP_RDP:0x1:r
PCROP1B_STRT:0x1FF:r
PCROP1B_END:0x0:r
WRP1A_STRT:0xFF:r
WRP1A_END:0x0:r
WRP1B_STRT:0xFF:r
WRP1B_END:0x0:r

91
scripts/otp.py Executable file
View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
import logging
import argparse
import os
import sys
import re
import struct
import datetime
class Main:
def __init__(self):
# command args
self.parser = argparse.ArgumentParser()
self.parser.add_argument("-d", "--debug", action="store_true", help="Debug")
self.subparsers = self.parser.add_subparsers(help="sub-command help")
self.parser_generate = self.subparsers.add_parser(
"generate", help="OTP HW version generator"
)
self.parser_generate.add_argument(
"--version", type=int, help="Version", required=True
)
self.parser_generate.add_argument(
"--firmware", type=int, help="Firmware", required=True
)
self.parser_generate.add_argument(
"--body", type=int, help="Body", required=True
)
self.parser_generate.add_argument(
"--connect", type=int, help="Connect", required=True
)
self.parser_generate.add_argument(
"--name", type=str, help="Name", required=True
)
self.parser_generate.add_argument("file", help="Output file")
self.parser_generate.set_defaults(func=self.generate)
# 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 generate(self):
self.logger.debug(f"Generating OTP")
if self.args.name:
name = re.sub(
"[^a-zA-Z0-9.]", "", self.args.name
) # Filter all special characters
name = list(
map(str, map(ord, name[0:8]))
) # Strip to 8 chars and map to ascii codes
while len(name) < 8:
name.append("0")
n1, n2, n3, n4, n5, n6, n7, n8 = map(int, name)
data = struct.pack(
"<BBBBLBBBBBBBB",
self.args.version,
self.args.firmware,
self.args.body,
self.args.connect,
int(datetime.datetime.now().timestamp()),
n1,
n2,
n3,
n4,
n5,
n6,
n7,
n8,
)
open(self.args.file, "wb").write(data)
if __name__ == "__main__":
Main()()