sima-cli 0.0.22__py3-none-any.whl → 0.0.24__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 CHANGED
@@ -1,2 +1,2 @@
1
1
  # sima_cli/__version__.py
2
- __version__ = "0.0.22"
2
+ __version__ = "0.0.24"
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.nvme.nvme import nvme_format, nvme_remount
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, 'yocto', internal, flavor='headless')
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:
@@ -295,9 +298,9 @@ def install_cmd(ctx, component, version, mirror, tag):
295
298
 
296
299
  sima-cli install optiview
297
300
 
298
- sima-cli install -m https://example.com/packages/foo/metadata.json
301
+ sima-cli install -m https://custom-server/packages/foo/metadata.json
299
302
 
300
- sima-cli install examples.llima -v 1.7.0
303
+ sima-cli install samples/llima -v 1.7.0
301
304
  """
302
305
  internal = ctx.obj.get("internal", False)
303
306
 
@@ -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): Optional SDK version string (e.g., "1.7.0")
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
- return f"{base}/{metadata_name}"
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
- # Check if the entry already exists
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 "/dev/nvme0n1p1" in line or "/mnt/nvme" in line:
48
- click.echo("ℹ️ NVMe mount entry already exists in /etc/fstab.")
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 as sudo
52
- append_cmd = f"echo '{nvme_entry}' | sudo tee -a {fstab_path} > /dev/null"
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("✅ /etc/fstab updated to include NVMe auto-mount.")
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"❌ Failed to update /etc/fstab: {e}")
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)
@@ -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 "exlr".
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
- wic_file = next((f for f in file_list if f.endswith(".wic")), None)
323
- if not wic_file:
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: {wic_file}")
331
- write_bootimg(wic_file)
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
- fw_path = f"{board}"
10
- aql_query = f"""
11
- items.find({{
12
- "repo": "soc-images",
13
- "path": {{
14
- "$match": "{fw_path}/*"
15
- }},
16
- "type": "folder"
17
- }}).include("repo", "path", "name")
18
- """.strip()
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
- # Reconstruct full paths and remove board prefix
33
- full_paths = {
34
- f"{item['path']}/{item['name']}".replace(fw_path + "/", "")
35
- for item in results
36
- }
37
-
38
- # Extract top-level folders
39
- top_level_folders = sorted({path.split("/")[0] for path in full_paths})
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
- def _list_available_firmware_versions_external(board: str, match_keyword: str = None, flavor: str = 'headless'):
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}/{fwtype}/'
74
- f'simaai-devkit-fw-{board}-{fwtype}-{match_keyword}.tar.gz'
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:
@@ -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
- image_file = 'release.tar.gz' if flavor == 'headless' else 'graphics.tar.gz'
65
- download_url = url.rstrip("/") + f"/soc-images/{board}/{version_or_url}/artifacts/{image_file}"
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
- if base_name in target_filenames:
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
- # If it's a .wic.gz file, decompress it now
219
+ # Handle .wic.gz decompression
193
220
  if full_dest_path.endswith(".wic.gz"):
194
- uncompressed_path = full_dest_path[:-3] # remove .gz
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'):
@@ -460,7 +489,7 @@ def perform_update(version_or_url: str, ip: str = None, internal: bool = False,
460
489
  flavor = 'headless'
461
490
 
462
491
  if 'http' not in version_or_url and not os.path.exists(version_or_url):
463
- version_or_url = _pick_from_available_versions(board, version_or_url, internal, flavor=flavor)
492
+ version_or_url = _pick_from_available_versions(board, version_or_url, internal, flavor=flavor, swtype='yocto')
464
493
 
465
494
  extracted_paths = _download_image(version_or_url, board, internal, flavor=flavor)
466
495
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sima-cli
3
- Version: 0.0.22
3
+ Version: 0.0.24
4
4
  Summary: CLI tool for SiMa Developer Portal to download models, firmware, and apps.
5
5
  Home-page: https://developer.sima.ai/
6
6
  Author: SiMa.ai
@@ -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=lZAZ5KAyL1sCs70SHAhw90CQC4944919Ga30NNcR4_g,49
4
- sima_cli/cli.py,sha256=dO6MG8sBX7LKJ-FhPQf7bw8oqSlosDXKcBaVbCIDZWQ,16056
3
+ sima_cli/__version__.py,sha256=DCV3CPnzjzbtXgXeERW-emqWYofdoXVyCuOmLEUcm4Y,49
4
+ sima_cli/cli.py,sha256=9bgPaOOdFvkxNWmGQiRX2Ed1mFtLObyYMOFX0r3mLcw,16900
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=mtwtBEMmLh6-Hqtv6CTTAQa76sRJXZCqE9ORPQutfcc,16547
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=jsxMv7OlrDP_fjzfTMn5UoiSOv7afB2LSM0pR50b4uE,13541
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=9yCW1ZQl42DAWV_7sbNsqEKeS9FzHdvgXpY5eS2GpDs,3540
36
- sima_cli/update/remote.py,sha256=uv0cezLeG4tsJvalgm_VDOo3EUCU7LB3nXl8mNFFtds,10934
37
- sima_cli/update/updater.py,sha256=vBdT0Im0a0iKwB-LzVDZasnXk2Rq-kNlBGr7bTG0-14,20879
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=HnaLlK1AT9m0D07Cuts03EOiLTu0gzf6pbOvRBESBf8,22279
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.22.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
47
+ sima_cli-0.0.24.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.22.dist-info/METADATA,sha256=3LetoBbyq_AopItmkPCqz3BBWL2_osKmaPHd_Vh8038,3705
56
- sima_cli-0.0.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- sima_cli-0.0.22.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
58
- sima_cli-0.0.22.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
59
- sima_cli-0.0.22.dist-info/RECORD,,
56
+ sima_cli-0.0.24.dist-info/METADATA,sha256=MlS0hFX_RczbmeY1_GIYBnomsRhCYFaZVDlZTwa3M9k,3705
57
+ sima_cli-0.0.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
58
+ sima_cli-0.0.24.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
59
+ sima_cli-0.0.24.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
60
+ sima_cli-0.0.24.dist-info/RECORD,,