sima-cli 0.0.37__py3-none-any.whl → 0.0.38__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.37"
2
+ __version__ = "0.0.38"
sima_cli/cli.py CHANGED
@@ -335,7 +335,7 @@ def bootimg_cmd(ctx, version, boardtype, netboot, autoflash, fwtype, rootfs):
335
335
  try:
336
336
  boardtype = boardtype if boardtype != 'mlsoc' else 'davinci'
337
337
  if netboot or autoflash:
338
- setup_netboot(version, boardtype, internal, autoflash, flavor='headless', rootfs=rootfs)
338
+ setup_netboot(version, boardtype, internal, autoflash, flavor='headless', rootfs=rootfs, swtype=fwtype)
339
339
  click.echo("✅ Netboot image prepared and TFTP server is running.")
340
340
  else:
341
341
  write_image(version, boardtype, fwtype, internal, flavor='headless')
@@ -9,11 +9,9 @@ import tarfile
9
9
  import zipfile
10
10
  import stat
11
11
  import shlex
12
- from urllib.parse import urlparse
13
-
12
+ from urllib.parse import urlparse, quote, urljoin
14
13
  from typing import Dict
15
14
  from tqdm import tqdm
16
- from urllib.parse import urljoin
17
15
  from pathlib import Path
18
16
  import subprocess
19
17
  import requests
@@ -24,7 +22,7 @@ from rich.panel import Panel
24
22
  from huggingface_hub import snapshot_download
25
23
 
26
24
  from sima_cli.utils.disk import check_disk_space
27
- from sima_cli.utils.env import get_environment_type, get_exact_devkit_type
25
+ from sima_cli.utils.env import get_environment_type, get_exact_devkit_type, get_sima_build_version
28
26
  from sima_cli.download.downloader import download_file_from_url
29
27
  from sima_cli.install.metadata_validator import validate_metadata, MetadataValidationError
30
28
  from sima_cli.install.metadata_info import print_metadata_summary, parse_size_string_to_bytes
@@ -162,7 +160,7 @@ def _download_requirements_wheels(repo_dir: Path):
162
160
  req_file = deps_dir / "requirements.txt"
163
161
 
164
162
  if not req_file.exists():
165
- click.echo("⚠️ No requirements.txt found under resources/dependencies, skipping wheel download.")
163
+ click.echo("⚠️ No requirements.txt found under resources/dependencies in the repo, skipping wheel download, safe to ignore this message")
166
164
  return
167
165
 
168
166
  deps_dir.mkdir(parents=True, exist_ok=True)
@@ -216,7 +214,15 @@ def _download_github_repo(owner: str, repo: str, ref: str, dest_folder: str, tok
216
214
  Returns:
217
215
  str: Path to the extracted repo
218
216
  """
219
- url = f"https://api.github.com/repos/{owner}/{repo}/tarball/{ref}" if ref else f"https://api.github.com/repos/{owner}/{repo}/tarball"
217
+ # Encode ref for API, but sanitize separately for filesystem usage
218
+ if ref:
219
+ ref_encoded = quote(ref, safe="") # safe for URL
220
+ ref_safe = ref.replace("/", "_") # safe for filesystem
221
+ url = f"https://api.github.com/repos/{owner}/{repo}/tarball/{ref_encoded}"
222
+ else:
223
+ ref_encoded = ref_safe = None
224
+ url = f"https://api.github.com/repos/{owner}/{repo}/tarball"
225
+
220
226
  headers = {}
221
227
  if token:
222
228
  headers["Authorization"] = f"Bearer {token}"
@@ -233,11 +239,14 @@ def _download_github_repo(owner: str, repo: str, ref: str, dest_folder: str, tok
233
239
  tmp_file.write(chunk)
234
240
  tmp_path = Path(tmp_file.name)
235
241
 
236
- repo_dir = Path(dest_folder) / repo
242
+ # Use sanitized ref in folder name (if provided)
243
+ repo_dir_name = f"{repo}-{ref_safe}" if ref_safe else repo
244
+ repo_dir = Path(dest_folder) / repo_dir_name
237
245
  repo_dir.mkdir(parents=True, exist_ok=True)
238
246
 
239
247
  _extract_tar_strip_top_level(tmp_path, repo_dir)
240
248
  tmp_path.unlink(missing_ok=True)
249
+
241
250
  click.echo(f"✅ Downloaded GitHub repo to folder: {repo_dir}")
242
251
  _download_requirements_wheels(repo_dir=repo_dir)
243
252
 
@@ -300,9 +309,10 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
300
309
  org, name = repo_id.split("/", 1)
301
310
  target_dir = os.path.join(dest_folder, name)
302
311
 
303
- click.echo(f"🤗 Downloading Hugging Face repo: {repo_id}" + (f"@{revision}" if revision else ""))
312
+ click.echo(f"🤗 Downloading Hugging Face repo: {org}/{repo_id}" + (f"@{revision}" if revision else ""))
304
313
  model_path = snapshot_download(
305
314
  repo_id=repo_id,
315
+ revision=revision,
306
316
  local_dir=target_dir,
307
317
  local_dir_use_symlinks=False
308
318
  )
@@ -311,6 +321,7 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
311
321
 
312
322
  if resource.startswith("gh:"):
313
323
  resource_spec = resource[3:]
324
+
314
325
  if "@" in resource_spec:
315
326
  repo_id, ref = resource_spec.split("@", 1)
316
327
  else:
@@ -740,13 +751,19 @@ def _run_installation_script(metadata: Dict, extract_path: str = "."):
740
751
 
741
752
  print("✅ Installation completed successfully.")
742
753
 
743
- def _resolve_github_metadata_url(gh_ref: str) -> str:
754
+ def _resolve_github_metadata_url(gh_ref: str) -> tuple[str, str]:
744
755
  """
745
- Resolve a GitHub shorthand like gh:org/repo@tag into a raw URL for metadata.json.
756
+ Resolve a GitHub shorthand like gh:org/repo@tag into a local metadata.json file path.
746
757
  If tag is omitted, defaults to 'main'.
758
+
759
+ Args:
760
+ gh_ref (str): Reference in the form 'gh:org/repo@tag'
761
+
762
+ Returns:
763
+ tuple[str, str]: (local_path_to_metadata_json, tag_used)
747
764
  """
748
765
  try:
749
- _, repo_ref = gh_ref.split(":", 1) # remove 'gh:'
766
+ _, repo_ref = gh_ref.split(":", 1) # strip 'gh:'
750
767
  if "@" in repo_ref:
751
768
  org_repo, tag = repo_ref.split("@", 1)
752
769
  else:
@@ -755,17 +772,25 @@ def _resolve_github_metadata_url(gh_ref: str) -> str:
755
772
  owner, repo = org_repo.split("/", 1)
756
773
  token = os.getenv("GITHUB_TOKEN")
757
774
 
758
- # Use GitHub API to fetch the metadata.json file
759
- api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/metadata.json?ref={tag}"
775
+ # Encode the ref safely for GitHub API
776
+ tag_encoded = quote(tag, safe="")
777
+
778
+ # GitHub API URL for raw file contents
779
+ api_url = (
780
+ f"https://api.github.com/repos/{owner}/{repo}/contents/metadata.json?ref={tag_encoded}"
781
+ )
760
782
  headers = {"Accept": "application/vnd.github.v3.raw"}
761
783
  if token:
762
- headers["Authorization"] = f"token {token}"
784
+ headers["Authorization"] = f"Bearer {token}"
763
785
 
764
786
  r = requests.get(api_url, headers=headers)
765
787
  r.raise_for_status()
766
-
767
- # Write metadata.json locally so downstream logic can use a file/URL
768
- local_path = os.path.join("/tmp", f"{repo}-{tag}-metadata.json")
788
+
789
+ # --- Sanitize tag for filesystem use ---
790
+ tag_safe = tag.replace("/", "_")
791
+
792
+ # Write metadata.json locally
793
+ local_path = os.path.join("/tmp", f"{repo}-{tag_safe}-metadata.json")
769
794
  with open(local_path, "wb") as f:
770
795
  f.write(r.content)
771
796
 
@@ -804,22 +829,32 @@ def metadata_resolver(component: str, version: str = None, tag: str = None) -> s
804
829
 
805
830
  Args:
806
831
  component (str): Component name (e.g., "examples.llima" or "assets/ragfps")
807
- version (str): Required unless component starts with "assets/"
832
+ version (str): Optional. If not provided, auto-detect from /etc/build.
808
833
  tag (str): Optional tag to use (e.g., "dev")
809
834
 
810
835
  Returns:
811
836
  str: Fully qualified metadata URL
812
837
  """
838
+
813
839
  if tag:
814
840
  metadata_name = f"metadata-{tag}.json"
815
841
  else:
816
842
  metadata_name = "metadata.json"
817
843
 
844
+ # --- Asset case, assets are SDK version agnostic ---
818
845
  if component.startswith("assets/"):
819
846
  return f"https://docs.sima.ai/{component}/{metadata_name}"
820
847
 
848
+ # --- Auto-detect SDK version if missing ---
821
849
  if not version:
822
- raise ValueError("Version (-v) is required for non-asset components.")
850
+ core_version, _ = get_sima_build_version()
851
+ if core_version:
852
+ version = core_version
853
+ else:
854
+ raise ValueError(
855
+ "Version (-v) is required and could not be auto-detected "
856
+ "from /etc/build or /etc/buildinfo."
857
+ )
823
858
 
824
859
  sdk_path = f"SDK{version}"
825
860
  return f"https://docs.sima.ai/pkg_downloads/{sdk_path}/{component}/{metadata_name}"
@@ -8,6 +8,7 @@ class MetadataValidationError(Exception):
8
8
 
9
9
  VALID_TYPES = {"board", "palette", "host"}
10
10
  VALID_OS = {"linux", "windows", "mac"}
11
+ VALID_DEVKIT_SW = {"yocto", "elxr"}
11
12
 
12
13
  def validate_metadata(data: dict):
13
14
  # Top-level required fields
@@ -34,6 +35,15 @@ def validate_metadata(data: dict):
34
35
  if not isinstance(platform["compatible_with"], list):
35
36
  raise MetadataValidationError(f"'compatible_with' must be a list in entry {i}")
36
37
 
38
+ # ✅ Check optional devkit_sw
39
+ if "devkit_sw" in platform:
40
+ devkit_sw_value = platform["devkit_sw"].lower()
41
+ if devkit_sw_value not in VALID_DEVKIT_SW:
42
+ raise MetadataValidationError(
43
+ f"Invalid 'devkit_sw' value '{platform['devkit_sw']}' in platform entry {i}. "
44
+ f"Must be one of {VALID_DEVKIT_SW}"
45
+ )
46
+
37
47
  if "os" in platform:
38
48
  if not isinstance(platform["os"], list):
39
49
  raise MetadataValidationError(f"'os' must be a list in entry {i}")
@@ -81,7 +91,6 @@ def validate_metadata(data: dict):
81
91
  )
82
92
 
83
93
  try:
84
- # Extract number (e.g., "30GB" → 30.0)
85
94
  float(size_str[:-2].strip())
86
95
  except ValueError:
87
96
  raise MetadataValidationError(
@@ -135,4 +144,4 @@ def main():
135
144
  sys.exit(1)
136
145
 
137
146
  if __name__ == "__main__":
138
- main()
147
+ main()
@@ -52,7 +52,9 @@ def flash_emmc(client_manager, emmc_image_paths):
52
52
 
53
53
  for path in emmc_image_paths:
54
54
  click.echo(f"📤 Copying {path} to {selected_ip}:{remote_dir}")
55
- success = copy_file_to_remote_board(selected_ip, path, remote_dir, passwd=DEFAULT_PASSWORD)
55
+ success = copy_file_to_remote_board(
56
+ selected_ip, path, remote_dir, passwd=DEFAULT_PASSWORD
57
+ )
56
58
  if not success:
57
59
  click.echo(f"❌ Failed to copy {path} to {selected_ip}. Aborting.")
58
60
  return
@@ -61,36 +63,44 @@ def flash_emmc(client_manager, emmc_image_paths):
61
63
  ssh = init_ssh_session(selected_ip, password=DEFAULT_PASSWORD)
62
64
 
63
65
  # Step a: Check if eMMC exists
64
- check_cmd = "[ -e /dev/mmcblk0p1 ] || (echo '❌ /dev/mmcblk0p1 not found'; exit 1)"
66
+ check_cmd = "[ -e /dev/mmcblk0 ] || (echo '❌ /dev/mmcblk0 not found'; exit 1)"
65
67
  run_remote_command(ssh, check_cmd)
66
68
 
67
- # Step b: Flash with bmaptool
68
- wic_path = next((p for p in emmc_image_paths if p.endswith(".wic.gz")), None)
69
- if not wic_path:
70
- click.echo("❌ No .wic.gz image found in emmc_image_paths.")
71
- return
72
-
73
- # Step c: umount the emmc
69
+ # Step b: umount eMMC
74
70
  pre_unmount_cmd = (
75
71
  "sudo mount | grep mmcblk0 | awk '{print $3}' | while read mnt; do "
76
72
  "sudo umount \"$mnt\"; done"
77
73
  )
78
74
  run_remote_command(ssh, pre_unmount_cmd)
79
75
 
80
- # Step d: bmaptool copy the image
81
- filename = os.path.basename(wic_path)
82
- remote_path = f"/tmp/{filename}"
83
- flash_cmd = f"sudo bmaptool copy {remote_path} /dev/mmcblk0"
84
- run_remote_command(ssh, flash_cmd)
85
-
86
- # Step d: Fix GPT
87
- fix_cmd = 'sudo printf "fix\n" | sudo parted ---pretend-input-tty /dev/mmcblk0 print'
88
- run_remote_command(ssh, fix_cmd)
76
+ # Step c: Decide flashing method
77
+ wic_path = next((p for p in emmc_image_paths if p.endswith(".wic.gz")), None)
78
+ img_path = next((p for p in emmc_image_paths if p.endswith(".img")), None)
79
+
80
+ if wic_path:
81
+ filename = os.path.basename(wic_path)
82
+ remote_path = f"/tmp/{filename}"
83
+ flash_cmd = f"sudo bmaptool copy {remote_path} /dev/mmcblk0"
84
+ run_remote_command(ssh, flash_cmd)
85
+
86
+ # Step d: Fix GPT for Yocto
87
+ fix_cmd = 'sudo printf "fix\n" | sudo parted ---pretend-input-tty /dev/mmcblk0 print'
88
+ run_remote_command(ssh, fix_cmd)
89
+
90
+ elif img_path:
91
+ filename = os.path.basename(img_path)
92
+ remote_path = f"/tmp/{filename}"
93
+ flash_cmd = f"sudo dd if={remote_path} of=/dev/mmcblk0 bs=4M conv=fsync status=progress"
94
+ run_remote_command(ssh, flash_cmd)
95
+ else:
96
+ click.echo("❌ No .wic.gz or .img image found in emmc_image_paths.")
97
+ return
89
98
 
90
99
  click.echo("✅ Flash completed. Please reboot the board to boot from eMMC.")
91
100
  except Exception as e:
92
101
  click.echo(f"❌ Flashing failed: {e}")
93
102
 
103
+
94
104
  class ClientManager:
95
105
  """Manages TFTP client state and monitoring."""
96
106
  def __init__(self):
@@ -337,7 +347,7 @@ def run_cli(client_manager):
337
347
  else:
338
348
  click.echo("📭 No TFTP client requests received yet.")
339
349
  elif user_input == "f":
340
- click.echo("🔧 Initiating eMMC flash (implementation pending).")
350
+ click.echo(f"🔧 Initiating eMMC flash {emmc_image_paths}.")
341
351
  flash_emmc(client_manager, emmc_image_paths)
342
352
  elif user_input == "":
343
353
  continue
@@ -347,7 +357,7 @@ def run_cli(client_manager):
347
357
  click.echo("\n🛑 Exiting netboot session.")
348
358
  return True
349
359
 
350
- def setup_netboot(version: str, board: str, internal: bool = False, autoflash: bool = False, flavor: str = 'headless', rootfs: str = ''):
360
+ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: bool = False, flavor: str = 'headless', rootfs: str = '', swtype: str = 'yocto'):
351
361
  """
352
362
  Download and serve a bootable image for network boot over TFTP with client monitoring.
353
363
 
@@ -358,6 +368,7 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
358
368
  autoflash (bool): Whether to automatically flash the devkit when networked booted. Defaults to False.
359
369
  flavor (str): The software flavor, can be either headless or full.
360
370
  rootfs (str): The root fs folder, which contains the .wic.gz file and the .bmap file, for custom image writing.
371
+ swtype (str): The software type, either yocto or elxr.
361
372
 
362
373
  Raises:
363
374
  RuntimeError: If the download or TFTP setup fails.
@@ -370,8 +381,8 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
370
381
  exit(1)
371
382
 
372
383
  try:
373
- click.echo(f"⬇️ Downloading netboot image for version: {version}, board: {board}")
374
- file_list = download_image(version, board, swtype="yocto", internal=internal, update_type='netboot', flavor=flavor)
384
+ click.echo(f"⬇️ Downloading netboot image for version: {version}, board: {board}, swtype: {swtype}")
385
+ file_list = download_image(version, board, swtype=swtype, internal=internal, update_type='netboot', flavor=flavor)
375
386
  if not isinstance(file_list, list):
376
387
  raise ValueError("Expected list of extracted files, got something else.")
377
388
  extract_dir = os.path.dirname(file_list[0])
@@ -380,7 +391,8 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
380
391
  # Extract specific image paths
381
392
  wic_gz_file = next((f for f in file_list if f.endswith(".wic.gz")), None)
382
393
  bmap_file = next((f for f in file_list if f.endswith(".wic.bmap")), None)
383
- emmc_image_paths = [p for p in [wic_gz_file, bmap_file] if p]
394
+ elxr_img_file = next((f for f in file_list if f.endswith(".img")), None)
395
+ emmc_image_paths = [p for p in [wic_gz_file, bmap_file, elxr_img_file] if p]
384
396
 
385
397
  # Check global custom_rootfs before doing anything else
386
398
  custom_rootfs = rootfs
@@ -391,13 +403,14 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
391
403
  import glob
392
404
  wic_gz_file = next(iter(glob.glob(os.path.join(custom_rootfs, "*.wic.gz"))), None)
393
405
  bmap_file = next(iter(glob.glob(os.path.join(custom_rootfs, "*.wic.bmap"))), None)
406
+ exlr_file = next(iter(glob.glob(os.path.join(custom_rootfs, "*.img"))), None)
394
407
 
395
408
  if not (wic_gz_file and bmap_file):
396
409
  raise RuntimeError(
397
410
  f"❌ custom_rootfs '{custom_rootfs}' must contain both .wic.gz and .wic.bmap files."
398
411
  )
399
412
 
400
- emmc_image_paths = [wic_gz_file, bmap_file]
413
+ emmc_image_paths = [wic_gz_file, bmap_file, exlr_file]
401
414
  click.echo(f"📁 Using custom_rootfs: {custom_rootfs}")
402
415
 
403
416
  click.echo(f"📁 eMMC image paths are: {emmc_image_paths}")
@@ -65,7 +65,7 @@ def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = Fals
65
65
  image_file = 'release.tar.gz' if flavor == 'headless' else 'graphics.tar.gz'
66
66
  download_url = url.rstrip("/") + f"/soc-images/{board}/{version_or_url}/artifacts/{image_file}"
67
67
  elif swtype == 'elxr':
68
- image_file = 'elxr-palette-modalix-1.7.0-arm64.img.gz' # temporary, this should change.
68
+ image_file = f'{board}-tftp-boot.tar.gz'
69
69
  download_url = url.rstrip("/") + f"/soc-images/elxr/{board}/{version_or_url}/artifacts/palette/{image_file}"
70
70
 
71
71
  return download_url
@@ -203,6 +203,8 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
203
203
  f"{board}-som.dtb",
204
204
  f"{board}-dvt.dtb",
205
205
  f"{board}-hhhl_x16.dtb",
206
+ f"pcie-4rc-2rc-2rc.dtbo",
207
+ f"elxr-palette-{board}-arm64.cpio.gz",
206
208
  f"simaai-image-{_flavor}-{board}.wic.gz",
207
209
  f"simaai-image-{_flavor}-{board}.wic.bmap",
208
210
  f"simaai-image-{_flavor}-{board}.cpio.gz"
@@ -321,9 +323,26 @@ def _download_image(version_or_url: str, board: str, internal: bool = False, upd
321
323
  # Download the file
322
324
  click.echo(f"📦 Downloading from {image_url}")
323
325
  firmware_path = download_file_from_url(image_url, dest_path, internal=internal)
326
+ extracted_files = _extract_required_files(firmware_path, board, update_type, flavor)
327
+
328
+ # If internal, netboot and elxr, we need to download some additional files to prepare for eMMC flash.
329
+ if internal and update_type == "netboot" and swtype == "elxr":
330
+ base_url = os.path.dirname(image_url)
331
+ base_version = version_or_url.split('_')[0]
332
+ extra_files = [f"elxr-palette-{board}-{base_version}-arm64.img.gz"]
333
+
334
+ for fname in extra_files:
335
+ extra_url = f"{base_url}/{fname}"
336
+ try:
337
+ click.echo(f"📥 Downloading extra file: {fname} from {extra_url} saving into {dest_path}")
338
+ netboot_file_path = download_file_from_url(extra_url, dest_path, internal=internal)
339
+ extracted_files.extend(_extract_required_files(netboot_file_path, board, update_type, flavor))
340
+ click.echo(f"✅ Saved {fname} to {dest_path}")
341
+ except Exception as e:
342
+ click.echo(f"⚠️ Failed to download {fname}: {e}")
324
343
 
325
344
  click.echo(f"📦 Firmware downloaded to: {firmware_path}")
326
- return _extract_required_files(firmware_path, board, update_type, flavor)
345
+ return extracted_files
327
346
 
328
347
  except Exception as e:
329
348
  click.echo(f"❌ Host update failed: {e}")
@@ -471,7 +490,7 @@ def download_image(version_or_url: str, board: str, swtype: str, internal: bool
471
490
  Args:
472
491
  version_or_url (str): Either a version string (e.g., "1.6.0") or a direct URL or local file path to the image.
473
492
  board (str): The board type (e.g., "mlsoc", "modalix").
474
- swtype (str): The software type (default to 'davinci', possible values: `davinci`, `modalix`): not supported for now
493
+ swtype (str): The software type (default to 'yocto', possible values: `yocto`, `elxr`): not supported for now
475
494
  internal (bool): Whether to use internal download paths (e.g., Artifactory).
476
495
  update_type (str): Whether this is standard update or writing boot image.
477
496
  flavor (str): Flavor of the image, can be headless or full.
sima_cli/utils/env.py CHANGED
@@ -2,7 +2,8 @@ import os
2
2
  import subprocess
3
3
  import platform
4
4
  import shutil
5
- from typing import Tuple
5
+ import re
6
+ from typing import Tuple, Optional
6
7
 
7
8
  # Utility functions to determine the environment:
8
9
  # - Whether we are running on a SiMa board
@@ -33,6 +34,38 @@ def is_sima_board() -> bool:
33
34
 
34
35
  return False
35
36
 
37
+ def get_sima_build_version() -> Tuple[Optional[str], Optional[str]]:
38
+ """
39
+ Retrieve the current SiMa build version from /etc/build or /etc/buildinfo.
40
+
41
+ It searches for 'SIMA_BUILD_VERSION=' and extracts:
42
+ - core_version: the semantic version (e.g., '2.0.0')
43
+ - full_version: the complete build string (e.g., '2.0.0_develop_B1932')
44
+
45
+ Returns:
46
+ tuple: (core_version, full_version)
47
+ If not found, returns (None, None)
48
+ """
49
+ build_file_paths = ["/etc/build", "/etc/buildinfo"]
50
+ version_pattern = re.compile(r"SIMA_BUILD_VERSION\s*=\s*([\w\.\-\+]+)")
51
+
52
+ for path in build_file_paths:
53
+ if os.path.exists(path):
54
+ try:
55
+ with open(path, "r") as f:
56
+ for line in f:
57
+ match = version_pattern.search(line)
58
+ if match:
59
+ full_version = match.group(1)
60
+ # Extract only major.minor.patch portion
61
+ core_match = re.match(r"(\d+\.\d+\.\d+)", full_version)
62
+ core_version = core_match.group(1) if core_match else None
63
+ return core_version, full_version
64
+ except Exception:
65
+ continue
66
+
67
+ return None, None
68
+
36
69
  def is_pcie_host() -> bool:
37
70
  """
38
71
  Detect if running from a PCIe host (typically a Linux or macOS dev machine).
@@ -71,6 +104,31 @@ def get_sima_board_type() -> str:
71
104
 
72
105
  return ""
73
106
 
107
+ def is_devkit_running_elxr() -> bool:
108
+ """
109
+ Check if the system is a SiMa devkit and is running ELXR software.
110
+
111
+ Conditions:
112
+ - Must be identified as a SiMa board via is_sima_board()
113
+ - /etc/buildinfo exists and contains "elxr" (case-insensitive)
114
+
115
+ Returns:
116
+ bool: True if SiMa devkit with ELXR software, False otherwise.
117
+ """
118
+ if not is_sima_board():
119
+ return False
120
+
121
+ buildinfo_path = "/etc/buildinfo"
122
+ if not os.path.exists(buildinfo_path):
123
+ return False
124
+
125
+ try:
126
+ with open(buildinfo_path, "r") as f:
127
+ content = f.read().lower()
128
+ return "elxr" in content
129
+ except Exception:
130
+ return False
131
+
74
132
  def is_modalix_devkit() -> bool:
75
133
  """
76
134
  Determines whether the current system is a Modalix DevKit (SoM)
@@ -41,16 +41,19 @@ def update_package(package_name: str):
41
41
  def has_internet(timeout: float = 1.0) -> bool:
42
42
  """
43
43
  Quick check for internet connectivity by connecting to a known DNS server.
44
+ First tries Cloudflare (1.1.1.1), falls back to Google (8.8.8.8).
44
45
  Uses IP to avoid DNS lookup delays.
45
46
  """
46
- try:
47
- socket.setdefaulttimeout(timeout)
48
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
49
- sock.connect(("1.1.1.1", 53))
50
- sock.close()
51
- return True
52
- except OSError:
53
- return False
47
+ def try_connect(ip: str, port: int = 53) -> bool:
48
+ try:
49
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
50
+ sock.settimeout(timeout)
51
+ sock.connect((ip, port))
52
+ return True
53
+ except OSError:
54
+ return False
55
+
56
+ return try_connect("1.1.1.1") or try_connect("8.8.8.8")
54
57
 
55
58
  def check_for_update(package_name: str, timeout: float = 2.0):
56
59
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sima-cli
3
- Version: 0.0.37
3
+ Version: 0.0.38
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=4m-QBPuK5Old2YxxO8B7YbrZhHPXQ0fWWTNKgReu9TE,49
4
- sima_cli/cli.py,sha256=uPfpXDkmX_bv5s1zjUD_pZabxnRxihg6ad_2obs9IZ8,19276
3
+ sima_cli/__version__.py,sha256=Vmt5txS5JnPSX3Xq_JSjKsimA5tK-R4xOPnyOu6NbeI,49
4
+ sima_cli/cli.py,sha256=jGufO70y0NGLsIfEhr1pQNdFoBdzMxTO1R7p386d4-8,19291
5
5
  sima_cli/app_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  sima_cli/app_zoo/app.py,sha256=6u3iIqVZkuMK49kK0f3dVJCCf5-qC-xzLOS78-TkeN8,15738
7
7
  sima_cli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -14,8 +14,8 @@ sima_cli/download/downloader.py,sha256=UQdrBWLQsPQygaoVaufOjbzWmRoNnk6pgLdnbnVi0
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=DtfuyBy0eGm6itL6wC9d4tj16nIDRxvszEQ7z2ogW74,30306
18
- sima_cli/install/metadata_validator.py,sha256=7954rp9vFRNnqmIMvCVTjq40kUIEbGXzfc8HmQmChe0,5221
17
+ sima_cli/install/metadata_installer.py,sha256=ZvR1bb8qie0e1v_UCpZOhtWPm83Bhi1oBQK69f8Ajhg,31462
18
+ sima_cli/install/metadata_validator.py,sha256=Xp51J68hMvpQeX-11kPLgz1YarQZ2m-95gGpfe3-D3Q,5644
19
19
  sima_cli/install/optiview.py,sha256=r4DYdQDTUbZVCR87hl5T21gsjZrhqpU8hWnYxKmUJ_k,4790
20
20
  sima_cli/install/palette.py,sha256=uRznoHa4Mv9ZXHp6AoqknfC3RxpYNKi9Ins756Cyifk,3930
21
21
  sima_cli/mla/meminfo.py,sha256=ndc8kQJmWGEIdvNh6iIhATGdrkqM2pbddr_eHxaPNfg,1466
@@ -33,20 +33,20 @@ sima_cli/update/bmaptool.py,sha256=KrhUGShBwY4Wzz50QiuMYAxxPgEy1nz5C68G-0a4qF4,4
33
33
  sima_cli/update/bootimg.py,sha256=OA_GyZwI8dbU3kaucKmoxAZbnnSnjXeOkU6yuDPji1k,13632
34
34
  sima_cli/update/cleanlog.py,sha256=-V6eDl3MdsvDmCfkKUJTqkXJ_WnLJE01uxS7z96b15g,909
35
35
  sima_cli/update/local.py,sha256=z3oRk6JH-zbCdoS3JpgeW_ZB4kolG7nPhLC55A2yssk,5597
36
- sima_cli/update/netboot.py,sha256=hsJQLq4HVwFFkaWjA54VZdkMGDhO0RmylciS78qAfrM,19663
36
+ sima_cli/update/netboot.py,sha256=V0QiqyHzAdrucJ87Q3Xlm2EBvkpjpu4RwPRWw9NXRcs,20334
37
37
  sima_cli/update/query.py,sha256=6RgvQfQT1_EtBGcibvVcz003dRKOq17NaGgL2mhaBbY,4891
38
38
  sima_cli/update/remote.py,sha256=wC4MSBQVxmibxtPBchAzFMhZYcRjxTiLlPSzVI0en4o,14690
39
- sima_cli/update/updater.py,sha256=NPd32OzM_356Nm1bEDDNh7faubW_VSOk12J4I6Yc2SQ,24580
39
+ sima_cli/update/updater.py,sha256=CYOXTV-8zIo92Lv2vyBzKtUyrtFBwkJiknttlS-UxF4,25648
40
40
  sima_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  sima_cli/utils/artifactory.py,sha256=6YyVpzVm8ATy7NEwT9nkWx-wptkXrvG7Wl_zDT6jmLs,2390
42
42
  sima_cli/utils/config.py,sha256=wE-cPQqY_gOqaP8t01xsRHD9tBUGk9MgBUm2GYYxI3E,1616
43
43
  sima_cli/utils/config_loader.py,sha256=7I5we1yiCai18j9R9jvhfUzAmT3OjAqVK35XSLuUw8c,2005
44
44
  sima_cli/utils/disk.py,sha256=66Kr631yhc_ny19up2aijfycWfD35AeLQOJgUsuH2hY,446
45
- sima_cli/utils/env.py,sha256=LtV8S1kCkOpi-7Gj4rhidQRN13x_NDKy4W_LxujheeI,8400
45
+ sima_cli/utils/env.py,sha256=0qY1-PJiI1G3uDVv774aimPXhHQBtY56FqXSMeMQTps,10401
46
46
  sima_cli/utils/net.py,sha256=WVntA4CqipkNrrkA4tBVRadJft_pMcGYh4Re5xk3rqo,971
47
47
  sima_cli/utils/network.py,sha256=UvqxbqbWUczGFyO-t1SybG7Q-x9kjUVRNIn_D6APzy8,1252
48
- sima_cli/utils/pkg_update_check.py,sha256=IAV_NAOsBDL_lYNYMRYfdZWuVq-rJ_zzHjJJZ7UQaoc,3274
49
- sima_cli-0.0.37.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
48
+ sima_cli/utils/pkg_update_check.py,sha256=GD0XVS2_l2WpuzL81TKkEQIDHXXffJeF3LELefncHNM,3467
49
+ sima_cli-0.0.38.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
50
50
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  tests/test_app_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  tests/test_auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -55,8 +55,8 @@ tests/test_download.py,sha256=t87DwxlHs26_ws9rpcHGwr_OrcRPd3hz6Zmm0vRee2U,4465
55
55
  tests/test_firmware.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  tests/test_model_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
- sima_cli-0.0.37.dist-info/METADATA,sha256=q4R_yQkhPyP_qD9c7ZIwI5RN-2wnMVyw1mChmwrhG4o,3705
59
- sima_cli-0.0.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
- sima_cli-0.0.37.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
61
- sima_cli-0.0.37.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
62
- sima_cli-0.0.37.dist-info/RECORD,,
58
+ sima_cli-0.0.38.dist-info/METADATA,sha256=EMJTYog2SdzP3e1dSwkTvzIQ_glqF9tQs7zTZAaGrgc,3705
59
+ sima_cli-0.0.38.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
60
+ sima_cli-0.0.38.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
61
+ sima_cli-0.0.38.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
62
+ sima_cli-0.0.38.dist-info/RECORD,,