sima-cli 0.0.22__py3-none-any.whl → 0.0.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sima_cli/__version__.py +1 -1
- sima_cli/cli.py +31 -3
- sima_cli/install/metadata_installer.py +10 -10
- sima_cli/{nvme → storage}/nvme.py +30 -10
- sima_cli/storage/sdcard.py +136 -0
- sima_cli/update/bootimg.py +11 -6
- sima_cli/update/query.py +49 -26
- sima_cli/update/remote.py +8 -6
- sima_cli/update/updater.py +42 -13
- {sima_cli-0.0.22.dist-info → sima_cli-0.0.23.dist-info}/METADATA +1 -1
- {sima_cli-0.0.22.dist-info → sima_cli-0.0.23.dist-info}/RECORD +15 -14
- {sima_cli-0.0.22.dist-info → sima_cli-0.0.23.dist-info}/WHEEL +0 -0
- {sima_cli-0.0.22.dist-info → sima_cli-0.0.23.dist-info}/entry_points.txt +0 -0
- {sima_cli-0.0.22.dist-info → sima_cli-0.0.23.dist-info}/licenses/LICENSE +0 -0
- {sima_cli-0.0.22.dist-info → sima_cli-0.0.23.dist-info}/top_level.txt +0 -0
sima_cli/__version__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# sima_cli/__version__.py
|
2
|
-
__version__ = "0.0.
|
2
|
+
__version__ = "0.0.23"
|
sima_cli/cli.py
CHANGED
@@ -11,7 +11,8 @@ from sima_cli.install.optiview import install_optiview
|
|
11
11
|
from sima_cli.install.hostdriver import install_hostdriver
|
12
12
|
from sima_cli.install.metadata_installer import install_from_metadata, metadata_resolver
|
13
13
|
from sima_cli.serial.serial import connect_serial
|
14
|
-
from sima_cli.
|
14
|
+
from sima_cli.storage.nvme import nvme_format, nvme_remount
|
15
|
+
from sima_cli.storage.sdcard import sdcard_format
|
15
16
|
from sima_cli.network.network import network_menu
|
16
17
|
|
17
18
|
# Entry point for the CLI tool using Click's command group decorator
|
@@ -232,10 +233,11 @@ def show_mla_memory_usage(ctx):
|
|
232
233
|
@main.command(name="bootimg")
|
233
234
|
@click.option("-v", "--version", required=True, help="Firmware version to download and write (e.g., 1.6.0)")
|
234
235
|
@click.option("-b", "--boardtype", type=click.Choice(["modalix", "mlsoc"], case_sensitive=False), default="mlsoc", show_default=True, help="Target board type.")
|
236
|
+
@click.option("-t", "--fwtype", type=click.Choice(["yocto", "elxr"], case_sensitive=False), default="yocto", show_default=True, help="Target firmware type.")
|
235
237
|
@click.option("-n", "--netboot", is_flag=True, default=False, show_default=True, help="Prepare image for network boot and launch TFTP server.")
|
236
238
|
@click.option("-a", "--autoflash", is_flag=True, default=False, show_default=True, help="Net boot the DevKit and automatically flash the internal storage - TBD")
|
237
239
|
@click.pass_context
|
238
|
-
def bootimg_cmd(ctx, version, boardtype, netboot, autoflash):
|
240
|
+
def bootimg_cmd(ctx, version, boardtype, netboot, autoflash, fwtype):
|
239
241
|
"""
|
240
242
|
Download and burn a removable media or setup TFTP boot.
|
241
243
|
|
@@ -257,6 +259,7 @@ def bootimg_cmd(ctx, version, boardtype, netboot, autoflash):
|
|
257
259
|
click.echo(f"📦 Preparing boot image:")
|
258
260
|
click.echo(f" 🔹 Version : {version}")
|
259
261
|
click.echo(f" 🔹 Board Type: {boardtype}")
|
262
|
+
click.echo(f" 🔹 F/W Type : {fwtype}")
|
260
263
|
click.echo(f" 🔹 F/W Flavor: headless")
|
261
264
|
|
262
265
|
try:
|
@@ -265,7 +268,7 @@ def bootimg_cmd(ctx, version, boardtype, netboot, autoflash):
|
|
265
268
|
setup_netboot(version, boardtype, internal, autoflash, flavor='headless')
|
266
269
|
click.echo("✅ Netboot image prepared and TFTP server is running.")
|
267
270
|
else:
|
268
|
-
write_image(version, boardtype,
|
271
|
+
write_image(version, boardtype, fwtype, internal, flavor='headless')
|
269
272
|
click.echo("✅ Boot image successfully written.")
|
270
273
|
click.echo("✅ Boot image successfully written.")
|
271
274
|
except Exception as e:
|
@@ -420,6 +423,31 @@ def nvme_cmd(ctx, operation):
|
|
420
423
|
click.echo(f"❌ Unsupported NVMe operation: {operation}")
|
421
424
|
ctx.exit(1)
|
422
425
|
|
426
|
+
# ----------------------
|
427
|
+
# NVME Subcommands
|
428
|
+
# ----------------------
|
429
|
+
NVME_OPERATIONS = {"format"}
|
430
|
+
@main.command(name="sdcard")
|
431
|
+
@click.argument("operation", type=click.Choice(NVME_OPERATIONS, case_sensitive=False))
|
432
|
+
@click.pass_context
|
433
|
+
def sdcard_cmd(ctx, operation):
|
434
|
+
"""
|
435
|
+
Prepare the SD Card as a data storage device for MLSoc DevKit or Modalix Early Access Unit
|
436
|
+
|
437
|
+
Available operations:
|
438
|
+
|
439
|
+
format - Format the SD Card
|
440
|
+
|
441
|
+
Example:
|
442
|
+
sima-cli sdcard format
|
443
|
+
"""
|
444
|
+
operation = operation.lower()
|
445
|
+
|
446
|
+
if operation == "format":
|
447
|
+
sdcard_format()
|
448
|
+
|
449
|
+
|
450
|
+
|
423
451
|
# ----------------------
|
424
452
|
# App Zoo Subcommands
|
425
453
|
# ----------------------
|
@@ -425,23 +425,23 @@ def metadata_resolver(component: str, version: str = None, tag: str = None) -> s
|
|
425
425
|
Resolve the metadata.json URL for a given component and version/tag.
|
426
426
|
|
427
427
|
Args:
|
428
|
-
component (str): Component name (e.g., "examples.llima")
|
429
|
-
version (str):
|
428
|
+
component (str): Component name (e.g., "examples.llima" or "assets/ragfps")
|
429
|
+
version (str): Required unless component starts with "assets/"
|
430
430
|
tag (str): Optional tag to use (e.g., "dev")
|
431
431
|
|
432
432
|
Returns:
|
433
433
|
str: Fully qualified metadata URL
|
434
434
|
"""
|
435
|
-
if not version:
|
436
|
-
raise ValueError("Version (-v) is required for non-hardcoded components.")
|
437
|
-
|
438
|
-
# Normalize version for URL path
|
439
|
-
sdk_path = f"SDK{version}"
|
440
|
-
base = f"https://docs.sima.ai/pkg_downloads/{sdk_path}/{component}"
|
441
|
-
|
442
435
|
if tag:
|
443
436
|
metadata_name = f"metadata-{tag}.json"
|
444
437
|
else:
|
445
438
|
metadata_name = "metadata.json"
|
446
439
|
|
447
|
-
|
440
|
+
if component.startswith("assets/"):
|
441
|
+
return f"https://docs.sima.ai/{component}/{metadata_name}"
|
442
|
+
|
443
|
+
if not version:
|
444
|
+
raise ValueError("Version (-v) is required for non-asset components.")
|
445
|
+
|
446
|
+
sdk_path = f"SDK{version}"
|
447
|
+
return f"https://docs.sima.ai/pkg_downloads/{sdk_path}/{component}/{metadata_name}"
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import subprocess
|
2
2
|
import click
|
3
|
+
import re
|
4
|
+
|
3
5
|
from sima_cli.utils.env import is_modalix_devkit
|
4
6
|
|
5
7
|
def scan_nvme():
|
@@ -33,27 +35,45 @@ def format_nvme(lbaf_index):
|
|
33
35
|
|
34
36
|
def add_nvme_to_fstab():
|
35
37
|
"""
|
36
|
-
Add /dev/nvme0n1p1 to /etc/fstab for persistent mounting at /mnt/nvme.
|
38
|
+
Add UUID-based entry for /dev/nvme0n1p1 to /etc/fstab for persistent mounting at /mnt/nvme.
|
37
39
|
Only appends if the entry does not already exist.
|
38
|
-
Requires root permission to modify /etc/fstab.
|
40
|
+
Requires root permission to run blkid and modify /etc/fstab.
|
39
41
|
"""
|
42
|
+
device = "/dev/nvme0n1p1"
|
40
43
|
fstab_path = "/etc/fstab"
|
41
|
-
nvme_entry = "/dev/nvme0n1p1 /mnt/nvme ext4 defaults 0 2"
|
42
44
|
|
43
45
|
try:
|
44
|
-
#
|
46
|
+
# Get UUID and filesystem type using blkid
|
47
|
+
blkid_output = subprocess.check_output(["sudo", "blkid", device], text=True)
|
48
|
+
uuid_match = re.search(r'UUID="([^"]+)"', blkid_output)
|
49
|
+
type_match = re.search(r'TYPE="([^"]+)"', blkid_output)
|
50
|
+
|
51
|
+
if not uuid_match or not type_match:
|
52
|
+
click.echo("❌ Could not extract UUID or TYPE from blkid output.")
|
53
|
+
return
|
54
|
+
|
55
|
+
uuid = uuid_match.group(1)
|
56
|
+
fs_type = type_match.group(1)
|
57
|
+
|
58
|
+
# Construct the fstab line
|
59
|
+
fstab_entry = f"UUID={uuid} /mnt/nvme {fs_type} defaults,noatime,noauto 0 0"
|
60
|
+
|
61
|
+
# Check if entry already exists
|
45
62
|
with open(fstab_path, "r") as f:
|
46
63
|
for line in f:
|
47
|
-
if
|
48
|
-
click.echo("ℹ️ NVMe mount
|
64
|
+
if uuid in line or "/mnt/nvme" in line:
|
65
|
+
click.echo("ℹ️ NVMe UUID or mount point already exists in /etc/fstab.")
|
49
66
|
return
|
50
67
|
|
51
|
-
# Append the entry
|
52
|
-
append_cmd = f"echo '{
|
68
|
+
# Append the entry to fstab
|
69
|
+
append_cmd = f"echo '{fstab_entry}' | sudo tee -a {fstab_path} > /dev/null"
|
53
70
|
subprocess.run(append_cmd, shell=True, check=True)
|
54
|
-
click.echo("✅
|
71
|
+
click.echo(f"✅ Added NVMe UUID entry to /etc/fstab:\n{fstab_entry}")
|
72
|
+
|
73
|
+
except subprocess.CalledProcessError as e:
|
74
|
+
click.echo(f"❌ Failed to run blkid or append to fstab: {e}")
|
55
75
|
except Exception as e:
|
56
|
-
click.echo(f"❌
|
76
|
+
click.echo(f"❌ Unexpected error: {e}")
|
57
77
|
|
58
78
|
def mount_nvme():
|
59
79
|
try:
|
@@ -0,0 +1,136 @@
|
|
1
|
+
import click
|
2
|
+
import subprocess
|
3
|
+
import platform
|
4
|
+
import shutil
|
5
|
+
import sys
|
6
|
+
import os
|
7
|
+
import time
|
8
|
+
|
9
|
+
from sima_cli.update.bootimg import list_removable_devices, unmount_device, _require_sudo
|
10
|
+
from sima_cli.utils.env import is_sima_board
|
11
|
+
|
12
|
+
def get_partition_path(device: str) -> str:
|
13
|
+
"""For Linux: partition is device + '1'"""
|
14
|
+
return device + "1"
|
15
|
+
|
16
|
+
|
17
|
+
def find_mkfs_ext4() -> str:
|
18
|
+
"""Find mkfs.ext4 on Linux"""
|
19
|
+
mkfs_path = shutil.which("mkfs.ext4")
|
20
|
+
if mkfs_path and os.path.exists(mkfs_path):
|
21
|
+
return mkfs_path
|
22
|
+
return None
|
23
|
+
|
24
|
+
|
25
|
+
def kill_partition_users(device_path: str):
|
26
|
+
"""Kill processes using the partitions on the device"""
|
27
|
+
try:
|
28
|
+
output = subprocess.check_output(["lsblk", "-n", "-o", "NAME", device_path]).decode().strip().splitlines()
|
29
|
+
partitions = [f"/dev/{line.strip()}" for line in output if line.strip() and f"/dev/{line.strip()}" != device_path]
|
30
|
+
|
31
|
+
for p in partitions:
|
32
|
+
try:
|
33
|
+
users = subprocess.check_output(["sudo", "lsof", p], stderr=subprocess.DEVNULL).decode().splitlines()
|
34
|
+
pids = {line.split()[1] for line in users[1:] if line.strip()}
|
35
|
+
for pid in pids:
|
36
|
+
subprocess.run(["sudo", "kill", "-9", pid], check=False)
|
37
|
+
except subprocess.CalledProcessError:
|
38
|
+
# lsof exits 1 if no process is using the file
|
39
|
+
continue
|
40
|
+
|
41
|
+
# Let the system settle
|
42
|
+
time.sleep(1)
|
43
|
+
|
44
|
+
except Exception as e:
|
45
|
+
click.echo(f"⚠️ Could not resolve partition users: {e}")
|
46
|
+
|
47
|
+
def create_partition_table(device_path: str):
|
48
|
+
"""Linux: create a GPT partition table with one ext4 partition"""
|
49
|
+
click.echo(f"🧹 Wiping and partitioning {device_path} using parted (Linux)")
|
50
|
+
subprocess.run(["sudo", "parted", "-s", device_path, "mklabel", "gpt"], check=True)
|
51
|
+
subprocess.run(["sudo", "parted", "-s", device_path, "mkpart", "primary", "ext4", "0%", "100%"], check=True)
|
52
|
+
subprocess.run(["sudo", "partprobe", device_path], check=True)
|
53
|
+
|
54
|
+
|
55
|
+
def force_release_device(device_path: str):
|
56
|
+
"""
|
57
|
+
Try to forcefully release a device by killing users and removing mappings.
|
58
|
+
"""
|
59
|
+
# Ensure partitions like /dev/sdc1 don't block parted
|
60
|
+
subprocess.run(["sudo", "umount", device_path + "1"], stderr=subprocess.DEVNULL)
|
61
|
+
subprocess.run(["sudo", "dmsetup", "remove", device_path], stderr=subprocess.DEVNULL)
|
62
|
+
|
63
|
+
# Try lsof to find open handles
|
64
|
+
try:
|
65
|
+
out = subprocess.check_output(["sudo", "lsof", device_path], stderr=subprocess.DEVNULL).decode()
|
66
|
+
for line in out.splitlines()[1:]:
|
67
|
+
pid = line.split()[1]
|
68
|
+
subprocess.run(["sudo", "kill", "-9", pid], check=False)
|
69
|
+
except subprocess.CalledProcessError:
|
70
|
+
# Likely no users
|
71
|
+
pass
|
72
|
+
|
73
|
+
# Final probe reset
|
74
|
+
subprocess.run(["sudo", "partprobe"], stderr=subprocess.DEVNULL)
|
75
|
+
subprocess.run(["sudo", "udevadm", "settle"], stderr=subprocess.DEVNULL)
|
76
|
+
|
77
|
+
|
78
|
+
def sdcard_format():
|
79
|
+
"""Linux-only SD card formatter for ext4."""
|
80
|
+
|
81
|
+
if platform.system() != "Linux":
|
82
|
+
click.echo("❌ This command only supports Desktop Linux.")
|
83
|
+
sys.exit(1)
|
84
|
+
|
85
|
+
if is_sima_board():
|
86
|
+
click.echo("❌ This command does not run on the DevKit due to lack of mkfs.ext4 support.")
|
87
|
+
|
88
|
+
mkfs_path = find_mkfs_ext4()
|
89
|
+
if not mkfs_path:
|
90
|
+
click.echo("❌ mkfs.ext4 not found on this platform.")
|
91
|
+
sys.exit(1)
|
92
|
+
|
93
|
+
devices = list_removable_devices()
|
94
|
+
if not devices:
|
95
|
+
click.echo("⚠️ No removable SD card found.")
|
96
|
+
return
|
97
|
+
|
98
|
+
click.echo("\n🔍 Detected removable devices:")
|
99
|
+
for i, d in enumerate(devices):
|
100
|
+
click.echo(f"[{i}] {d['path']} - {d['size']} - {d['name']}")
|
101
|
+
|
102
|
+
selected_path = None
|
103
|
+
if len(devices) == 1:
|
104
|
+
if click.confirm(f"\n✅ Use device {devices[0]['path']}?"):
|
105
|
+
selected_path = devices[0]['path']
|
106
|
+
else:
|
107
|
+
choice = click.prompt("Enter the number of the device to format", type=int)
|
108
|
+
if 0 <= choice < len(devices):
|
109
|
+
selected_path = devices[choice]['path']
|
110
|
+
|
111
|
+
if not selected_path:
|
112
|
+
click.echo("❌ No device selected. Operation cancelled.")
|
113
|
+
return
|
114
|
+
|
115
|
+
click.echo(f"\n🚨 WARNING: This will ERASE ALL DATA on {selected_path}")
|
116
|
+
if not click.confirm("Are you sure you want to continue?"):
|
117
|
+
click.echo("❌ Aborted by user.")
|
118
|
+
return
|
119
|
+
|
120
|
+
_require_sudo()
|
121
|
+
unmount_device(selected_path)
|
122
|
+
force_release_device(selected_path)
|
123
|
+
kill_partition_users(selected_path)
|
124
|
+
|
125
|
+
try:
|
126
|
+
create_partition_table(selected_path)
|
127
|
+
partition_path = get_partition_path(selected_path)
|
128
|
+
|
129
|
+
click.echo(f"🧱 Formatting partition {partition_path} as ext4 using {mkfs_path}")
|
130
|
+
subprocess.run(["sudo", mkfs_path, "-F", partition_path], check=True)
|
131
|
+
|
132
|
+
click.echo(f"✅ Successfully formatted {partition_path} as ext4, insert this SD card into MLSoC or Modalix Early Access Kit")
|
133
|
+
|
134
|
+
except subprocess.CalledProcessError as e:
|
135
|
+
click.echo(f"❌ Formatting failed: {e}")
|
136
|
+
sys.exit(1)
|
sima_cli/update/bootimg.py
CHANGED
@@ -306,7 +306,7 @@ def write_image(version: str, board: str, swtype: str, internal: bool = False, f
|
|
306
306
|
Parameters:
|
307
307
|
version (str): Firmware version to download (e.g., "1.6.0").
|
308
308
|
board (str): Target board type, e.g., "modalix" or "mlsoc".
|
309
|
-
swtype (str): Software image type, e.g., "yocto" or "
|
309
|
+
swtype (str): Software image type, e.g., "yocto" or "elxr".
|
310
310
|
internal (bool): Whether to use internal download sources. Defaults to False.
|
311
311
|
flavor (str): Flavor of the software package - can be either headless or full.
|
312
312
|
|
@@ -318,17 +318,22 @@ def write_image(version: str, board: str, swtype: str, internal: bool = False, f
|
|
318
318
|
file_list = download_image(version, board, swtype, internal, update_type='bootimg', flavor=flavor)
|
319
319
|
if not isinstance(file_list, list):
|
320
320
|
raise ValueError("Expected list of extracted files, got something else.")
|
321
|
+
|
322
|
+
image_file = next(
|
323
|
+
(f for f in file_list if f.endswith(".wic") or f.endswith(".img")),
|
324
|
+
None
|
325
|
+
)
|
321
326
|
|
322
|
-
|
323
|
-
|
324
|
-
raise FileNotFoundError("No .wic image file found after extraction.")
|
327
|
+
if not image_file:
|
328
|
+
raise FileNotFoundError("No .wic or .img image file found after extraction.")
|
325
329
|
|
326
330
|
except Exception as e:
|
327
331
|
raise RuntimeError(f"❌ Failed to download image: {e}")
|
328
332
|
|
329
333
|
try:
|
330
|
-
click.echo(f"📝 Writing image to removable media: {
|
331
|
-
write_bootimg(
|
334
|
+
click.echo(f"📝 Writing image to removable media: {image_file}")
|
335
|
+
write_bootimg(image_file)
|
336
|
+
|
332
337
|
except Exception as e:
|
333
338
|
raise RuntimeError(f"❌ Failed to write image: {e}")
|
334
339
|
|
sima_cli/update/query.py
CHANGED
@@ -5,17 +5,32 @@ from sima_cli.utils.config import get_auth_token
|
|
5
5
|
|
6
6
|
ARTIFACTORY_BASE_URL = artifactory_url() + '/artifactory'
|
7
7
|
|
8
|
-
def _list_available_firmware_versions_internal(board: str, match_keyword: str = None, flavor: str = 'headless'):
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
"
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
def _list_available_firmware_versions_internal(board: str, match_keyword: str = None, flavor: str = 'headless', swtype: str = 'yocto'):
|
9
|
+
if swtype == 'yocto':
|
10
|
+
fw_path = f"{board}"
|
11
|
+
aql_query = f"""
|
12
|
+
items.find({{
|
13
|
+
"repo": "soc-images",
|
14
|
+
"path": {{
|
15
|
+
"$match": "{fw_path}/*"
|
16
|
+
}},
|
17
|
+
"type": "folder"
|
18
|
+
}}).include("repo", "path", "name")
|
19
|
+
""".strip()
|
20
|
+
elif swtype == 'elxr':
|
21
|
+
fw_path = f"elxr/{board}"
|
22
|
+
aql_query = f"""
|
23
|
+
items.find({{
|
24
|
+
"repo": "soc-images",
|
25
|
+
"path": {{
|
26
|
+
"$match": "{fw_path}/*/artifacts/palette"
|
27
|
+
}},
|
28
|
+
"name": "modalix-tftp-boot.tar.gz",
|
29
|
+
"type": "file"
|
30
|
+
}}).include("repo", "path", "name")
|
31
|
+
""".strip()
|
32
|
+
else:
|
33
|
+
raise ValueError(f"Unsupported swtype: {swtype}")
|
19
34
|
|
20
35
|
aql_url = f"{ARTIFACTORY_BASE_URL}/api/search/aql"
|
21
36
|
headers = {
|
@@ -29,14 +44,18 @@ def _list_available_firmware_versions_internal(board: str, match_keyword: str =
|
|
29
44
|
|
30
45
|
results = response.json().get("results", [])
|
31
46
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
47
|
+
if swtype == 'yocto':
|
48
|
+
# Reconstruct full paths and remove board prefix
|
49
|
+
full_paths = {
|
50
|
+
f"{item['path']}/{item['name']}".replace(fw_path + "/", "")
|
51
|
+
for item in results
|
52
|
+
}
|
53
|
+
top_level_folders = sorted({path.split("/")[0] for path in full_paths})
|
54
|
+
else: # elxr
|
55
|
+
# Extract version from path like: elxr/{board}/<version>/artifacts/palette
|
56
|
+
top_level_folders = sorted({
|
57
|
+
item['path'].split('/')[2] for item in results
|
58
|
+
})
|
40
59
|
|
41
60
|
if match_keyword:
|
42
61
|
match_keyword = match_keyword.lower()
|
@@ -46,7 +65,8 @@ def _list_available_firmware_versions_internal(board: str, match_keyword: str =
|
|
46
65
|
|
47
66
|
return top_level_folders
|
48
67
|
|
49
|
-
|
68
|
+
|
69
|
+
def _list_available_firmware_versions_external(board: str, match_keyword: str = None, flavor: str = 'headless', swtype: str = 'yocto'):
|
50
70
|
"""
|
51
71
|
Construct and return a list containing a single firmware download URL for a given board.
|
52
72
|
|
@@ -57,11 +77,11 @@ def _list_available_firmware_versions_external(board: str, match_keyword: str =
|
|
57
77
|
board (str): The name of the hardware board.
|
58
78
|
match_keyword (str, optional): A version string to match (e.g., '1.6' or '1.6.0').
|
59
79
|
flavor (str, optional): A string indicating firmware flavor - headless or full.
|
80
|
+
swtype (str, optional): A string indicating firmware type - yocto or elxr.
|
60
81
|
|
61
82
|
Returns:
|
62
83
|
list[str]: A list containing one formatted firmware download URL.
|
63
84
|
"""
|
64
|
-
fwtype = 'yocto'
|
65
85
|
cfg = load_resource_config()
|
66
86
|
download_url_base = cfg.get('public').get('download').get('download_url')
|
67
87
|
|
@@ -69,14 +89,17 @@ def _list_available_firmware_versions_external(board: str, match_keyword: str =
|
|
69
89
|
if re.fullmatch(r'\d+\.\d+', match_keyword):
|
70
90
|
match_keyword += '.0'
|
71
91
|
|
92
|
+
# If it's headless then don't append flavor str to the URL, otherwise add it.
|
93
|
+
flavor_str = 'full-' if flavor == 'full' else ''
|
94
|
+
|
72
95
|
firmware_download_url = (
|
73
|
-
f'{download_url_base}SDK{match_keyword}/devkit/{board}/{
|
74
|
-
f'simaai-devkit-fw-{board}-{
|
96
|
+
f'{download_url_base}SDK{match_keyword}/devkit/{board}/{swtype}/'
|
97
|
+
f'simaai-devkit-fw-{board}-{swtype}-{flavor_str}{match_keyword}.tar.gz'
|
75
98
|
)
|
76
99
|
return [firmware_download_url]
|
77
100
|
|
78
101
|
|
79
|
-
def list_available_firmware_versions(board: str, match_keyword: str = None, internal: bool = False, flavor: str = 'headless'):
|
102
|
+
def list_available_firmware_versions(board: str, match_keyword: str = None, internal: bool = False, flavor: str = 'headless', swtype: str = 'yocto'):
|
80
103
|
"""
|
81
104
|
Public interface to list available firmware versions.
|
82
105
|
|
@@ -90,6 +113,6 @@ def list_available_firmware_versions(board: str, match_keyword: str = None, inte
|
|
90
113
|
- List[str] of firmware version folder names, or None if access is not allowed
|
91
114
|
"""
|
92
115
|
if not internal:
|
93
|
-
return _list_available_firmware_versions_external(board, match_keyword, flavor)
|
116
|
+
return _list_available_firmware_versions_external(board, match_keyword, flavor, swtype)
|
94
117
|
|
95
|
-
return _list_available_firmware_versions_internal(board, match_keyword, flavor)
|
118
|
+
return _list_available_firmware_versions_internal(board, match_keyword, flavor, swtype)
|
sima_cli/update/remote.py
CHANGED
@@ -237,13 +237,8 @@ def push_and_update_remote_board(ip: str, troot_path: str, palette_path: str, pa
|
|
237
237
|
|
238
238
|
# Run Palette update
|
239
239
|
_flavor = 'palette' if flavor == 'headless' else 'graphics'
|
240
|
-
run_remote_command(
|
241
|
-
ssh,
|
242
|
-
f"sudo swupdate -H simaai-image-{_flavor}:1.0 -i /tmp/{palette_name}",
|
243
|
-
password=passwd
|
244
|
-
)
|
245
|
-
click.echo("✅ Board image update complete.")
|
246
240
|
|
241
|
+
# Set necessary env first to make sure it can access NVMe device
|
247
242
|
if _flavor == 'graphics':
|
248
243
|
click.echo(f"⚠️ With full image, setting U-Boot environment variable to support NVMe and GPU.")
|
249
244
|
run_remote_command(
|
@@ -252,6 +247,13 @@ def push_and_update_remote_board(ip: str, troot_path: str, palette_path: str, pa
|
|
252
247
|
password=passwd
|
253
248
|
)
|
254
249
|
|
250
|
+
run_remote_command(
|
251
|
+
ssh,
|
252
|
+
f"sudo swupdate -H simaai-image-{_flavor}:1.0 -i /tmp/{palette_name}",
|
253
|
+
password=passwd
|
254
|
+
)
|
255
|
+
click.echo("✅ Board image update complete.")
|
256
|
+
|
255
257
|
# In the case of PCIe system, we don't need to reboot the card, instead, we will let it finish then update the PCIe driver in the host
|
256
258
|
# After that we can reboot the whole system.
|
257
259
|
if reboot_and_wait:
|
sima_cli/update/updater.py
CHANGED
@@ -29,7 +29,7 @@ else:
|
|
29
29
|
def convert_flavor(flavor: str = 'headless'):
|
30
30
|
return 'palette' if flavor == 'headless' else 'graphics'
|
31
31
|
|
32
|
-
def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = False, flavor: str = 'headless') -> str:
|
32
|
+
def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = False, flavor: str = 'headless', swtype: str = 'yocto') -> str:
|
33
33
|
"""
|
34
34
|
Resolve the final firmware download URL based on board, version, and environment.
|
35
35
|
|
@@ -38,6 +38,7 @@ def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = Fals
|
|
38
38
|
board (str): Board type ('davinci' or 'modalix').
|
39
39
|
internal (bool): Whether to use internal config for URL construction.
|
40
40
|
flavor (str): firmware image flavor, can be headless or full.
|
41
|
+
swtype (str): firmware image type, can be yocto or elxr
|
41
42
|
|
42
43
|
Returns:
|
43
44
|
str: Full download URL.
|
@@ -61,11 +62,16 @@ def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = Fals
|
|
61
62
|
if board == 'davinci':
|
62
63
|
flavor = 'headless'
|
63
64
|
|
64
|
-
|
65
|
-
|
65
|
+
if swtype == 'yocto':
|
66
|
+
image_file = 'release.tar.gz' if flavor == 'headless' else 'graphics.tar.gz'
|
67
|
+
download_url = url.rstrip("/") + f"/soc-images/{board}/{version_or_url}/artifacts/{image_file}"
|
68
|
+
elif swtype == 'elxr':
|
69
|
+
image_file = 'elxr-palette-modalix-1.7.0-arm64.img.gz' # temporary, this should change.
|
70
|
+
download_url = url.rstrip("/") + f"/soc-images/elxr/{board}/{version_or_url}/artifacts/palette/{image_file}"
|
71
|
+
|
66
72
|
return download_url
|
67
73
|
|
68
|
-
def _pick_from_available_versions(board: str, version_or_url: str, internal: bool, flavor: str) -> str:
|
74
|
+
def _pick_from_available_versions(board: str, version_or_url: str, internal: bool, flavor: str, swtype: str) -> str:
|
69
75
|
"""
|
70
76
|
Presents an interactive menu (with search) for selecting a firmware version.
|
71
77
|
"""
|
@@ -73,7 +79,7 @@ def _pick_from_available_versions(board: str, version_or_url: str, internal: boo
|
|
73
79
|
if "http" in version_or_url:
|
74
80
|
return version_or_url
|
75
81
|
|
76
|
-
available_versions = list_available_firmware_versions(board, version_or_url, internal, flavor)
|
82
|
+
available_versions = list_available_firmware_versions(board, version_or_url, internal, flavor, swtype)
|
77
83
|
|
78
84
|
try:
|
79
85
|
if len(available_versions) > 1:
|
@@ -136,7 +142,7 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
|
|
136
142
|
|
137
143
|
Returns:
|
138
144
|
list: List of full paths to extracted files.
|
139
|
-
"""
|
145
|
+
"""
|
140
146
|
extract_dir = os.path.dirname(tar_path)
|
141
147
|
_flavor = convert_flavor(flavor)
|
142
148
|
|
@@ -166,8 +172,28 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
|
|
166
172
|
f"simaai-image-{_flavor}-{board}.wic.bmap",
|
167
173
|
f"simaai-image-{_flavor}-{board}.cpio.gz"
|
168
174
|
}
|
175
|
+
|
169
176
|
extracted_paths = []
|
170
177
|
|
178
|
+
# Handle .img.gz downloaded directly (e.g., ELXR palette image)
|
179
|
+
if tar_path.endswith(".img.gz"):
|
180
|
+
extract_dir = os.path.dirname(tar_path)
|
181
|
+
uncompressed_path = tar_path[:-3] # Remove .gz → .img
|
182
|
+
|
183
|
+
if os.path.exists(uncompressed_path):
|
184
|
+
click.echo(f"⚠️ Skipping decompression: {uncompressed_path} already exists")
|
185
|
+
return [tar_path, uncompressed_path]
|
186
|
+
|
187
|
+
try:
|
188
|
+
with gzip.open(tar_path, 'rb') as f_in:
|
189
|
+
with open(uncompressed_path, 'wb') as f_out:
|
190
|
+
shutil.copyfileobj(f_in, f_out)
|
191
|
+
click.echo(f"📦 Decompressed .img: {uncompressed_path}")
|
192
|
+
return [tar_path, uncompressed_path]
|
193
|
+
except Exception as e:
|
194
|
+
click.echo(f"❌ Failed to decompress {tar_path}: {e}")
|
195
|
+
return []
|
196
|
+
|
171
197
|
try:
|
172
198
|
try:
|
173
199
|
tar = tarfile.open(tar_path, mode="r:gz")
|
@@ -177,7 +203,8 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
|
|
177
203
|
with tar:
|
178
204
|
for member in tar.getmembers():
|
179
205
|
base_name = os.path.basename(member.name)
|
180
|
-
|
206
|
+
|
207
|
+
if base_name in target_filenames or base_name.endswith(".img.gz"):
|
181
208
|
full_dest_path = os.path.join(extract_dir, member.name)
|
182
209
|
|
183
210
|
if os.path.exists(full_dest_path):
|
@@ -189,9 +216,9 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
|
|
189
216
|
click.echo(f"✅ Extracted: {full_dest_path}")
|
190
217
|
extracted_paths.append(full_dest_path)
|
191
218
|
|
192
|
-
#
|
219
|
+
# Handle .wic.gz decompression
|
193
220
|
if full_dest_path.endswith(".wic.gz"):
|
194
|
-
uncompressed_path = full_dest_path[:-3]
|
221
|
+
uncompressed_path = full_dest_path[:-3]
|
195
222
|
|
196
223
|
if os.path.exists(uncompressed_path):
|
197
224
|
click.echo(f"⚠️ Skipping decompression: {uncompressed_path} already exists")
|
@@ -216,8 +243,9 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
|
|
216
243
|
click.echo(f"❌ Failed to extract files from archive: {e}")
|
217
244
|
return []
|
218
245
|
|
246
|
+
|
219
247
|
|
220
|
-
def _download_image(version_or_url: str, board: str, internal: bool = False, update_type: str = 'standard', flavor: str = 'headless'):
|
248
|
+
def _download_image(version_or_url: str, board: str, internal: bool = False, update_type: str = 'standard', flavor: str = 'headless', swtype: str = 'yocto'):
|
221
249
|
"""
|
222
250
|
Download or use a firmware image for the specified board and version or file path.
|
223
251
|
|
@@ -226,6 +254,7 @@ def _download_image(version_or_url: str, board: str, internal: bool = False, upd
|
|
226
254
|
board (str): Target board type ('davinci' or 'modalix').
|
227
255
|
internal (bool): Whether to use internal Artifactory resources.
|
228
256
|
flavor (str): Flavor of the image, can be headless or full, supported for Modalix only.
|
257
|
+
swtype (str): Type of the image, can be yocto or elxr, supported for all H/W platforms.
|
229
258
|
|
230
259
|
Notes:
|
231
260
|
- If a local file is provided, it skips downloading.
|
@@ -243,7 +272,7 @@ def _download_image(version_or_url: str, board: str, internal: bool = False, upd
|
|
243
272
|
image_url = version_or_url
|
244
273
|
else:
|
245
274
|
# Case 3: Resolve standard version string (Artifactory/AWS)
|
246
|
-
image_url = _resolve_firmware_url(version_or_url, board, internal, flavor=flavor)
|
275
|
+
image_url = _resolve_firmware_url(version_or_url, board, internal, flavor=flavor, swtype=swtype)
|
247
276
|
|
248
277
|
# Determine platform-safe temp directory
|
249
278
|
temp_dir = tempfile.gettempdir()
|
@@ -414,9 +443,9 @@ def download_image(version_or_url: str, board: str, swtype: str, internal: bool
|
|
414
443
|
"""
|
415
444
|
|
416
445
|
if 'http' not in version_or_url and not os.path.exists(version_or_url):
|
417
|
-
version_or_url = _pick_from_available_versions(board, version_or_url, internal, flavor)
|
446
|
+
version_or_url = _pick_from_available_versions(board, version_or_url, internal, flavor, swtype)
|
418
447
|
|
419
|
-
extracted_paths = _download_image(version_or_url, board, internal, update_type, flavor=flavor)
|
448
|
+
extracted_paths = _download_image(version_or_url, board, internal, update_type, flavor=flavor, swtype=swtype)
|
420
449
|
return extracted_paths
|
421
450
|
|
422
451
|
def perform_update(version_or_url: str, ip: str = None, internal: bool = False, passwd: str = "edgeai", auto_confirm: bool = False, flavor: str = 'headless'):
|
@@ -1,7 +1,7 @@
|
|
1
1
|
sima_cli/__init__.py,sha256=Nb2jSg9-CX1XvSc1c21U9qQ3atINxphuNkNfmR-9P3o,332
|
2
2
|
sima_cli/__main__.py,sha256=ehzD6AZ7zGytC2gLSvaJatxeD0jJdaEvNJvwYeGsWOg,69
|
3
|
-
sima_cli/__version__.py,sha256=
|
4
|
-
sima_cli/cli.py,sha256=
|
3
|
+
sima_cli/__version__.py,sha256=uP54joL5aVt8APefW7CPeBCn_PlfOZtxwbefmF7sYaI,49
|
4
|
+
sima_cli/cli.py,sha256=crfrcrBW-SsxtVJFGCDTU42Z342Yk_Isp0gynjKmc3c,16899
|
5
5
|
sima_cli/app_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
sima_cli/app_zoo/app.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
sima_cli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -14,7 +14,7 @@ sima_cli/download/downloader.py,sha256=nCBrr_0WdnKTIyecwKpg1sCdfm_4PSQTRPwEbiezy
|
|
14
14
|
sima_cli/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
sima_cli/install/hostdriver.py,sha256=kAWDLebs60mbWIyTbUxmNrChcKW1uD5r7FtWNSUVUE4,5852
|
16
16
|
sima_cli/install/metadata_info.py,sha256=wmMqwzGfXbuilkqaxRVrFOzOtTOiONkmPCyA2oDAQpA,2168
|
17
|
-
sima_cli/install/metadata_installer.py,sha256=
|
17
|
+
sima_cli/install/metadata_installer.py,sha256=vC7FamvGnE-WbI2AC3PAjJmMkP33IUW8D8ZzKrePPzs,16615
|
18
18
|
sima_cli/install/metadata_validator.py,sha256=7954rp9vFRNnqmIMvCVTjq40kUIEbGXzfc8HmQmChe0,5221
|
19
19
|
sima_cli/install/optiview.py,sha256=i5eWVor-9MScEfrQm3Ty9OP4VpSsCgWvNh7AvYdZu7s,3365
|
20
20
|
sima_cli/install/palette.py,sha256=uRznoHa4Mv9ZXHp6AoqknfC3RxpYNKi9Ins756Cyifk,3930
|
@@ -22,19 +22,20 @@ sima_cli/mla/meminfo.py,sha256=ndc8kQJmWGEIdvNh6iIhATGdrkqM2pbddr_eHxaPNfg,1466
|
|
22
22
|
sima_cli/model_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
23
|
sima_cli/model_zoo/model.py,sha256=q91Nrg62j1TqwPO8HiX4nlEFCCmzNEFcyFTBVMbJm8w,9836
|
24
24
|
sima_cli/network/network.py,sha256=ToDCQBfX0bUFEWWtfS8srImK5T11MX6R4MBQFM80faY,7617
|
25
|
-
sima_cli/nvme/nvme.py,sha256=ECdd25fvFbs5T5_PlIwnxm3NiPNmqnFGXrgLZhLENRY,4129
|
26
25
|
sima_cli/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
26
|
sima_cli/sdk/syscheck.py,sha256=h9zCULW67y4i2hqiGc-hc1ucBDShA5FAe9NxwBGq-fM,4575
|
28
27
|
sima_cli/serial/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
28
|
sima_cli/serial/serial.py,sha256=6xRta_PzE_DmmooYq35lbK76TYpAny5SEJAdYC_3fH0,4141
|
29
|
+
sima_cli/storage/nvme.py,sha256=WHeNoz0r8vQwtcrcLSf8IQsj4rc_eVt8KlxhX0wwk3M,4855
|
30
|
+
sima_cli/storage/sdcard.py,sha256=7X0r80U87YmJorV5CHLVzvXFtZcj6Agjv4y5M2UOKiQ,5031
|
30
31
|
sima_cli/update/__init__.py,sha256=0P-z-rSaev40IhfJXytK3AFWv2_sdQU4Ry6ei2sEus0,66
|
31
32
|
sima_cli/update/bmaptool.py,sha256=KrhUGShBwY4Wzz50QiuMYAxxPgEy1nz5C68G-0a4qF4,4988
|
32
|
-
sima_cli/update/bootimg.py,sha256=
|
33
|
+
sima_cli/update/bootimg.py,sha256=Eg8ZSp8LMZXbOMxX4ZPCjFOg3YEufmsVfojKrRc3fug,13631
|
33
34
|
sima_cli/update/local.py,sha256=Blje7O2pcBopBLXwuVI826lnjPMTJ3lPU85dTUWUV48,3445
|
34
35
|
sima_cli/update/netboot.py,sha256=RqFgBhixcjPEwdVGvKhR0TeztoFnmGigmXlA71WVksA,18647
|
35
|
-
sima_cli/update/query.py,sha256=
|
36
|
-
sima_cli/update/remote.py,sha256
|
37
|
-
sima_cli/update/updater.py,sha256=
|
36
|
+
sima_cli/update/query.py,sha256=b6Su7OlBGooIDcpb3_xH55tqkzznWcd_Fg1MkaNR874,4680
|
37
|
+
sima_cli/update/remote.py,sha256=-AACggSRpReMI1Pw37wLJK5qm_Gfin-5jGiw5ggQWrU,11008
|
38
|
+
sima_cli/update/updater.py,sha256=YXPfMdV29H3pu5bmLe-B3hpn3nQWlL5bwNytsN52Ces,22263
|
38
39
|
sima_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
40
|
sima_cli/utils/artifactory.py,sha256=6YyVpzVm8ATy7NEwT9nkWx-wptkXrvG7Wl_zDT6jmLs,2390
|
40
41
|
sima_cli/utils/config.py,sha256=wE-cPQqY_gOqaP8t01xsRHD9tBUGk9MgBUm2GYYxI3E,1616
|
@@ -43,7 +44,7 @@ sima_cli/utils/disk.py,sha256=66Kr631yhc_ny19up2aijfycWfD35AeLQOJgUsuH2hY,446
|
|
43
44
|
sima_cli/utils/env.py,sha256=bNushG2BD243fNlqCpuUJxLF76inRxTFeSDkl_KCHy0,7130
|
44
45
|
sima_cli/utils/net.py,sha256=WVntA4CqipkNrrkA4tBVRadJft_pMcGYh4Re5xk3rqo,971
|
45
46
|
sima_cli/utils/network.py,sha256=UvqxbqbWUczGFyO-t1SybG7Q-x9kjUVRNIn_D6APzy8,1252
|
46
|
-
sima_cli-0.0.
|
47
|
+
sima_cli-0.0.23.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
|
47
48
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
49
|
tests/test_app_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
50
|
tests/test_auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -52,8 +53,8 @@ tests/test_download.py,sha256=t87DwxlHs26_ws9rpcHGwr_OrcRPd3hz6Zmm0vRee2U,4465
|
|
52
53
|
tests/test_firmware.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
54
|
tests/test_model_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
55
|
tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
-
sima_cli-0.0.
|
56
|
-
sima_cli-0.0.
|
57
|
-
sima_cli-0.0.
|
58
|
-
sima_cli-0.0.
|
59
|
-
sima_cli-0.0.
|
56
|
+
sima_cli-0.0.23.dist-info/METADATA,sha256=jE_xgF2mw2x2Gxqt_K7lnukeFEtSTE-Z_6dvJBrixlk,3705
|
57
|
+
sima_cli-0.0.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
58
|
+
sima_cli-0.0.23.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
|
59
|
+
sima_cli-0.0.23.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
|
60
|
+
sima_cli-0.0.23.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|