مقایسه کامیت‌‌ها

..
3 کامیت‌ها
مولف SHA1 پیام تاریخ
nearology 3c470a8fea اضافه شدن ورک فلو تست شبکه
flash-aminpa-V1.5.yml / Hardware flashing - Amin Pardaz Asia v1.5 (push) Successful in 7m20s
2026-11-10 09:03:56 +03:30
nearology b5adbec417 اضافه شدن ورک فلو ریبوت نهایی 2026-11-10 08:58:02 +03:30
nearology 106084d6a4 اضافه شدن ورک فلو بوتcmd 2026-11-10 08:55:17 +03:30
6فایلهای تغییر یافته به همراه172 افزوده شده و 4 حذف شده
@@ -0,0 +1,15 @@
name: Configure Boot Command for Detected Flash
on:
workflow_dispatch:
jobs:
set-bootcmd:
runs-on: serial-runner
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Detect flash, set bootcmd and verify
run: /usr/bin/python3 scripts/serial_command.py set-bootcmd
@@ -1,5 +1,5 @@
name: flash-aminpa-V1.5
# Production flash for Amin Pardaz Asia device v1.5
name: flash-aminpa-V1.5.yml
# Production hardware flashing for Amin Pardaz Asia device v1.5
on:
push:
@@ -12,7 +12,7 @@ concurrency:
jobs:
flash-aminpa:
name: Production flash - Amin Pardaz Asia v1.5
name: Hardware flashing - Amin Pardaz Asia v1.5
runs-on: serial-runner
steps:
@@ -63,3 +63,9 @@ jobs:
- name: Inspect boot configuration
run: /usr/bin/python3 scripts/serial_command.py boot-config
- name: Configure and verify bootcmd for detected flash
run: /usr/bin/python3 scripts/serial_command.py set-bootcmd
- name: Reboot hardware after successful flashing
run: /usr/bin/python3 scripts/serial_command.py reboot
@@ -0,0 +1,30 @@
name: Software test - Amin Pardaz Asia v1.5
on:
workflow_run:
workflows:
- flash-aminpa-V1.5.yml
types:
- completed
branches:
- main
workflow_dispatch:
jobs:
ping-device:
if: ${{ gitea.event_name == 'workflow_dispatch' || gitea.event.workflow_run.conclusion == 'success' }}
runs-on: serial-runner
steps:
- name: Checkout tested revision
uses: actions/checkout@v4
with:
ref: ${{ gitea.event.workflow_run.head_sha || gitea.sha }}
- name: Wait up to three minutes for device ping
env:
TEST_INTERFACE: ${{ vars.TEST_INTERFACE || 'eno1' }}
TEST_HOST_IP: ${{ vars.TEST_HOST_IP || '192.168.75.15' }}
TEST_DEVICE_IP: ${{ vars.TEST_DEVICE_IP }}
run: |
/usr/bin/python3 scripts/software_test.py --interface "$TEST_INTERFACE" --host-ip "$TEST_HOST_IP" --device-ip "$TEST_DEVICE_IP"
+39
مشاهده پرونده
@@ -0,0 +1,39 @@
# فلش و تست دستگاه Amin Pardaz Asia v1.5
workflow اصلی `flash-aminpa-V1.5.yml` با push به شاخهٔ `main` اجرا می‌شود.
workflow تست نرم‌افزاری برای اجرا پس از موفقیت آن و همچنین اجرای دستی تعریف شده است.
## متغیرهای Gitea برای تست نرم‌افزاری
متغیرها را در تنظیمات مخزن Gitea، بخش **Actions → Variables** تعریف کنید.
نام متغیرها باید دقیقاً مطابق جدول باشد.
| نام متغیر | کاربرد | پیش‌فرض | الزامی |
| --- | --- | --- | --- |
| `TEST_INTERFACE` | نام کارت شبکهٔ میزبان runner برای تست | `eno1` | خیر |
| `TEST_HOST_IP` | آدرس IPv4 میزبان runner روی کارت شبکهٔ تست | `192.168.75.15` | خیر |
| `TEST_DEVICE_IP` | آدرس IPv4 دستگاه فلش‌شده که باید به ping پاسخ دهد | ندارد | بله |
`TEST_HOST_IP` آدرس میزبان است، نه دستگاه هدف. مقدار `TEST_DEVICE_IP` باید
با آن متفاوت باشد و با IP دستگاه پس از بوت سیستم‌عامل مطابقت داشته باشد؛
این مقدار لزوماً همان `ipaddr` موقت در U-Boot نیست.
IP میزبان باید از قبل روی کارت شبکهٔ انتخاب‌شده تنظیم شده باشد و مسیر رسیدن
به دستگاه از همان کارت شبکه عبور کند. اسکریپت تنظیمات شبکهٔ میزبان را تغییر نمی‌دهد.
مقادیر IP به‌صورت آدرس IPv4 وارد می‌شوند، نه رنج یا CIDR.
اسکریپت با پایتون سیستم و ابزارهای سیستم `ip` و `ping` اجرا می‌شود.
پس از بررسی تنظیمات میزبان، حداکثر ۱۸۰ ثانیه منتظر پاسخ ping دستگاه می‌ماند؛
اولین پاسخ موفق نتیجهٔ `PASS` دارد. نبود پاسخ تا پایان مهلت یا خطای تنظیمات
باعث `FAIL` و شکست workflow می‌شود.
نمونهٔ اجرای دستی روی runner با جایگزین‌کردن IP واقعی دستگاه:
```bash
/usr/bin/python3 scripts/software_test.py \
--interface eno1 \
--host-ip 192.168.75.15 \
--device-ip <DEVICE_IP>
```
برای محل فایل‌های firmware و نقشهٔ فلش، [راهنمای firmware](firmware/README.md) را ببینید.
+24 -1
مشاهده پرونده
@@ -184,6 +184,20 @@ def erase_flash(cli):
print("\nFull-flash erase command succeeded; first 256 bytes verified as FF.")
def set_bootcmd(cli):
capacity = detect_flash(cli)
dtb_offset, dtb_size = LAYOUTS[capacity]["dtb"]
kernel_offset, kernel_size = LAYOUTS[capacity]["kernel"]
bootcmd = (
f"sf probe 0; sf read 0x41800000 {dtb_offset:#x} {dtb_size:#x}; "
f"sf read 0x42000000 {kernel_offset:#x} {kernel_size:#x}; "
"bootz 0x42000000 - 0x41800000"
)
cli.command("setenv bootcmd '" + bootcmd + "'")
cli.command("printenv bootcmd", r"^bootcmd=" + re.escape(bootcmd) + r"$")
print("\nDynamic bootcmd verified in RAM; environment not saved and bootcmd not executed.")
def boot_config(cli, confirmed=False):
cli.command(
"printenv bootcmd bootargs", allow_missing=("bootcmd", "bootargs"),
@@ -199,7 +213,7 @@ def boot_config(cli, confirmed=False):
def main():
parser = argparse.ArgumentParser()
parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs", "erase", "boot-config", "preflight"), default="check", nargs="?")
parser.add_argument("mode", choices=("check", "configure", "transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs", "erase", "boot-config", "preflight", "set-bootcmd", "reboot"), default="check", nargs="?")
parser.add_argument("--port", default="/dev/ttyUSB0")
parser.add_argument("--firmware", type=Path, default=None)
parser.add_argument("--rootfs-size", type=lambda value: int(value, 0), help="Actual RootFS partition size in bytes (decimal or 0x-prefixed hex)")
@@ -218,6 +232,12 @@ def main():
cli.detect()
if args.mode == "check":
return
if args.mode == "reboot":
print("\n>>> reset", flush=True)
port.write(b"reset\r")
port.flush()
print("Reset command sent; reboot completion has not been verified.", flush=True)
return
if args.mode == "preflight":
capacity = detect_flash(cli)
files = {"uboot": "u-boot-sunxi-with-spl.bin", "dtb": "sun8i-v3s-licheepi-zero-dock.dtb",
@@ -236,6 +256,9 @@ def main():
if args.mode == "boot-config":
boot_config(cli, confirmed=args.confirm_squashfs_mtdblock3)
return
if args.mode == "set-bootcmd":
set_bootcmd(cli)
return
if args.mode in ("transfer", "transfer-dtb", "transfer-kernel", "transfer-rootfs"):
transfer(cli, args.firmware, dtb=args.mode == "transfer-dtb", kernel=args.mode == "transfer-kernel", rootfs=args.mode == "transfer-rootfs", rootfs_size=args.rootfs_size)
return
+55
مشاهده پرونده
@@ -0,0 +1,55 @@
"""Wait up to three minutes for the flashed device to answer ICMP."""
import argparse
import ipaddress
import json
import subprocess
import sys
def test_device(interface, host_ip, device_ip):
host = str(ipaddress.IPv4Address(host_ip))
device = str(ipaddress.IPv4Address(device_ip))
if host == device:
raise ValueError('Device IP must differ from the test host IP')
if not interface or interface.startswith('-'):
raise ValueError('Invalid network interface')
result = subprocess.run(
['ip', '-j', '-4', 'addr', 'show', 'dev', interface],
check=True, capture_output=True, text=True, timeout=5,
)
addresses = json.loads(result.stdout)
if not any(info.get('local') == host for entry in addresses for info in entry.get('addr_info', [])):
raise ValueError(f'Host IP {host} is not configured on {interface}')
result = subprocess.run(
['ip', '-j', '-4', 'route', 'get', device, 'from', host],
check=True, capture_output=True, text=True, timeout=5,
)
routes = json.loads(result.stdout)
if not routes or any(route.get('dev') != interface for route in routes):
raise ValueError(f'Route to {device} does not use {interface}')
print(f'Waiting up to 180 seconds: {host} ({interface}) -> {device}', flush=True)
# -c 1 exits on the first reply; -w bounds the entire ping run.
result = subprocess.run(
['ping', '-4', '-n', '-B', '-I', host, '-c', '1', '-i', '1', '-W', '1', '-w', '180', device],
timeout=180,
)
if result.returncode:
raise RuntimeError(f'FAIL: {device} did not pass the ping test (exit {result.returncode})')
print(f'PASS: device {device} answered ping', flush=True)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--interface', default='eno1')
parser.add_argument('--host-ip', default='192.168.75.15')
parser.add_argument('--device-ip', required=True)
args = parser.parse_args()
test_device(args.interface, args.host_ip, args.device_ip)
if __name__ == '__main__':
try:
main()
except (ValueError, RuntimeError, OSError, subprocess.SubprocessError) as exc:
print(f'FAIL: {exc}', file=sys.stderr)
sys.exit(1)