diff --git a/firmware/tools/fast_flash_pp_and_copy_apps.py b/firmware/tools/fast_flash_pp_and_copy_apps.py new file mode 100644 index 00000000..97b01076 --- /dev/null +++ b/firmware/tools/fast_flash_pp_and_copy_apps.py @@ -0,0 +1,265 @@ +# Copyleft Whiterose from the Dark Army +# +# This file is part of PortaPack. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +# this tool allow you flash pp and copy the apps in fastest way. note that you need to have a sdcard switcher to switch sdcard to pc. +# although, if you don't have one, you still can take out the sdcard and plug it into your pc, but i think it would not pretty fast. + +#!/usr/bin/env python3 + +import sys +import glob +import serial +import serial.tools.list_ports +import os +import shutil +import time +import subprocess +import tempfile + +# config +SUDO_PASSWORD = "" +SDCARD_LABEL = "portapack" + + +def write_command(ser, command, wait_response=None, remove_echo=False, dont_wait_ch=False): + + # clean buffer + while True: + try: + if ser.in_waiting: + ser.read(ser.in_waiting) + else: + break + except: + break + + command_with_newline = command + "\r\n" + try: + ser.write(command_with_newline.encode()) + except Exception as e: + return f"write err: {str(e)}" + + response = "" + expected_echo = f"{command}\r\n" + wait_response_met = wait_response is None + + while True: + try: + if ser.in_waiting: + msg = ser.read(ser.in_waiting).decode('utf-8') + response += msg + + if wait_response and wait_response in response: + wait_response_met = True + + if wait_response_met and (dont_wait_ch or "ch> " in response): + if remove_echo: + return response.replace(expected_echo, "") + return response + + except serial.SerialTimeoutException: + continue + except Exception as e: + return f"e: {str(e)}" + +def get_serial_devices(): + ports = serial.tools.list_ports.comports() + + if not ports: + print("no serial device found") + return None + + portapack_device = None + for port in ports: + if port.product == "PortaPack Mayhem": + print(f"found your pp:") + print(f"device: {port.device}") + print(f"description: {port.description}") + print(f"hwid: {port.hwid}") + if port.manufacturer: + print(f"manufacturer: {port.manufacturer}") + if port.serial_number: + print(f"serial_number: {port.serial_number}") + portapack_device = port + break + + if not portapack_device: + print("no pp device found") + + return portapack_device + +def run_sudo_command(cmd): + + if not SUDO_PASSWORD: + print("sudo password not set, please hard code it in the script") + return + + try: + process = subprocess.Popen( + ['sudo', '-S'] + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True + ) + stdout, stderr = process.communicate(input=SUDO_PASSWORD + '\n') + if process.returncode != 0: + raise subprocess.CalledProcessError(process.returncode, cmd, stdout, stderr) + return stdout + except subprocess.CalledProcessError as e: + print(f"sudo command failed: {e}") + raise + +def get_pp_device_linux(): + try: + cmd = ["lsblk", "-o", "NAME,LABEL,FSTYPE,SIZE,MOUNTPOINT", "-n", "-p"] + result = subprocess.run(cmd, capture_output=True, text=True) + + # find your pp sd + for line in result.stdout.splitlines(): + clean_line = line.replace('└─', '').replace('├─', '').strip() + parts = clean_line.split() + + if len(parts) >= 2 and SDCARD_LABEL in parts[1]: # checker + device_path = parts[0] + # if path valid + if not os.path.exists(device_path): + continue + + # if mounted, return mountpoint + if len(parts) >= 5: + mountpoint = parts[4] + return device_path, mountpoint + + # if not, create temp dir and mount + mount_point = tempfile.mkdtemp() + print(f"mount {device_path} to {mount_point}") + run_sudo_command(["mount", "-o", f"uid={os.getuid()},gid={os.getgid()}", device_path, mount_point]) + return device_path, mount_point + + return None, None + except subprocess.CalledProcessError as e: + print(f"command failed: {str(e)}") + return None, None + +def wait_for_pp_drive(): + while True: + if os.name == 'nt': # win didn't test yet, TODO + drives = [d + ':' for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if os.path.exists(d + ':')] + for drive in drives: + if os.path.basename(os.path.abspath(drive)).lower() == SDCARD_LABEL: + return drive, None + else: # linux and mac + device_path, mount_point = get_pp_device_linux() + if device_path and mount_point: + return mount_point, device_path + time.sleep(1) + +def run(): + # worker 1: check device connected + device = get_serial_devices() + if not device: + print("no pp device found") + return + + try: + # worker 2: open serial and send hackrf cmd + ser = serial.Serial(device.device, baudrate=115200, timeout=1) + for i in range(2): + write_command(ser, "hackrf") + ser.close() + + # worker 3: hint user to swtich sdcard to pc + print("\033[92m\n\t+------------------------+") + print("\t| switch PP sdcard to PC |") + print("\t+------------------------+\033[0m") + print("wait PP sdcard...") + + # worker 4: wait pp sd + mount_point, device_path = wait_for_pp_drive() + if not mount_point: + print("no pp sd found") + return + print(f"find pp sd: {mount_point}") + + # worker 5: copy ppma and ppmp apps + apps_dir = os.path.join(mount_point, 'APPS') + if not os.path.exists(apps_dir): + os.makedirs(apps_dir) + + firmware_dir = os.path.join('.', 'firmware', 'application') + for ext in ['ppma', 'ppmp']: + for file in glob.glob(os.path.join(firmware_dir, f'*.{ext}')): + dest = os.path.join(apps_dir, os.path.basename(file)) + print(f"cp: {os.path.basename(file)}") + shutil.copy2(file, dest) + + print("apps copy done") + + # worker 6: umount pp sd + if os.name != 'nt' and device_path: + run_sudo_command(["umount", mount_point]) + os.rmdir(mount_point) # 删除临时挂载点 + + # worker 7: run flash cmd with hackrf_spiflash util + print("\nflash...") + process = subprocess.Popen( + ["hackrf_spiflash", "-w", "./firmware/portapack-mayhem-firmware.bin"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True + ) + + while True: + output = process.stdout.readline() + if output: + print(output.strip()) + if process.poll() is not None: + break + + if process.returncode != 0: + raise subprocess.CalledProcessError(process.returncode, "hackrf_spiflash") + + # worker 8: hint user to swtich sdcard switcher back to pp + print("\033[92m\n\t+------------------------------------------------+") + print("\t| switch sdcard switcher back to pp, then reboot |") + print("\t+------------------------------------------------+\033[0m") + + except serial.SerialException as e: + print(f"serial comm error: {str(e)}") + except OSError as e: + print(f"file operation error: {str(e)}") + except subprocess.CalledProcessError as e: + print(f"system command error: {str(e)}") + except Exception as e: + print(f"error: {str(e)}") + # ensure umount when error + if os.name != 'nt' and 'mount_point' in locals() and device_path: + try: + run_sudo_command(["umount", mount_point]) + os.rmdir(mount_point) + except: + pass + +if __name__ == "__main__": + try: + run() + except Exception as e: + print(f"error: {str(e)}") + sys.exit(1) diff --git a/firmware/tools/make_spi_image.py b/firmware/tools/make_spi_image.py index 11180b30..bb797b51 100755 --- a/firmware/tools/make_spi_image.py +++ b/firmware/tools/make_spi_image.py @@ -161,3 +161,23 @@ write_image(spi_image, output_path) percent_remaining = round(1000 * pad_size / spi_size) / 10; print("Space remaining in flash ROM:", pad_size, "bytes (", percent_remaining, "%)") + + +# copy the fast flash script +build_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'build') +flash_py_path = os.path.join(build_dir, 'flash.py') + +if not os.path.exists(flash_py_path): + + current_dir = os.path.dirname(__file__) + source_file = os.path.join(current_dir, 'fast_flash_pp_and_copy_apps.py') + + if not os.path.exists(build_dir): + os.makedirs(build_dir) + + # cp + import shutil + print(f"\ncopy {source_file} to {flash_py_path}\n") + shutil.copy2(source_file, flash_py_path) + +