sima-cli 0.0.19__py3-none-any.whl → 0.0.20__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 +37 -8
- sima_cli/install/optiview.py +8 -3
- sima_cli/serial/__init__.py +0 -0
- sima_cli/serial/serial.py +114 -0
- sima_cli/update/bootimg.py +4 -4
- sima_cli/update/netboot.py +408 -0
- sima_cli/update/remote.py +41 -9
- sima_cli/update/updater.py +12 -1
- sima_cli/utils/net.py +29 -0
- {sima_cli-0.0.19.dist-info → sima_cli-0.0.20.dist-info}/METADATA +3 -1
- {sima_cli-0.0.19.dist-info → sima_cli-0.0.20.dist-info}/RECORD +16 -12
- {sima_cli-0.0.19.dist-info → sima_cli-0.0.20.dist-info}/WHEEL +0 -0
- {sima_cli-0.0.19.dist-info → sima_cli-0.0.20.dist-info}/entry_points.txt +0 -0
- {sima_cli-0.0.19.dist-info → sima_cli-0.0.20.dist-info}/licenses/LICENSE +0 -0
- {sima_cli-0.0.19.dist-info → sima_cli-0.0.20.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.20"
|
sima_cli/cli.py
CHANGED
@@ -9,10 +9,11 @@ from sima_cli.__version__ import __version__
|
|
9
9
|
from sima_cli.utils.config import CONFIG_PATH
|
10
10
|
from sima_cli.install.optiview import install_optiview
|
11
11
|
from sima_cli.install.hostdriver import install_hostdriver
|
12
|
+
from sima_cli.serial.serial import connect_serial
|
12
13
|
|
13
14
|
# Entry point for the CLI tool using Click's command group decorator
|
14
15
|
@click.group()
|
15
|
-
@click.option('-i', '--internal', is_flag=True, help="Use internal Artifactory resources
|
16
|
+
@click.option('-i', '--internal', is_flag=True, help="Use internal Artifactory resources, Authorized Sima employees only")
|
16
17
|
@click.pass_context
|
17
18
|
def main(ctx, internal):
|
18
19
|
"""
|
@@ -226,18 +227,23 @@ def show_mla_memory_usage(ctx):
|
|
226
227
|
@main.command(name="bootimg")
|
227
228
|
@click.option("-v", "--version", required=True, help="Firmware version to download and write (e.g., 1.6.0)")
|
228
229
|
@click.option("--boardtype", type=click.Choice(["modalix", "mlsoc"], case_sensitive=False), default="mlsoc", show_default=True, help="Target board type.")
|
230
|
+
@click.option("--netboot", is_flag=True, default=False, show_default=True, help="Prepare image for network boot and launch TFTP server.")
|
231
|
+
@click.option("--autoflash", is_flag=True, default=False, show_default=True, help="Net boot the DevKit and automatically flash the internal storage")
|
229
232
|
@click.pass_context
|
230
|
-
def bootimg_cmd(ctx, version, boardtype):
|
233
|
+
def bootimg_cmd(ctx, version, boardtype, netboot, autoflash):
|
231
234
|
"""
|
232
|
-
Download and burn a
|
235
|
+
Download and burn a removable media or setup TFTP boot.
|
233
236
|
|
234
237
|
Examples:
|
235
238
|
sima-cli bootimg -v 1.6.0
|
236
239
|
sima-cli bootimg -v 1.6.0 --boardtype mlsoc
|
237
240
|
sima-cli bootimg -v 1.6.0 --boardtype modalix
|
241
|
+
sima-cli bootimg -v 1.6.0 --boardtype modalix --netboot
|
242
|
+
sima-cli bootimg -v 1.6.0 --boardtype modalix --autoflash
|
238
243
|
"""
|
239
244
|
|
240
245
|
from sima_cli.update.bootimg import write_image
|
246
|
+
from sima_cli.update.netboot import setup_netboot
|
241
247
|
|
242
248
|
internal = ctx.obj.get("internal", False)
|
243
249
|
|
@@ -247,7 +253,12 @@ def bootimg_cmd(ctx, version, boardtype):
|
|
247
253
|
|
248
254
|
try:
|
249
255
|
boardtype = boardtype if boardtype != 'mlsoc' else 'davinci'
|
250
|
-
|
256
|
+
if netboot or autoflash:
|
257
|
+
setup_netboot(version, boardtype, internal, autoflash)
|
258
|
+
click.echo("✅ Netboot image prepared and TFTP server is running.")
|
259
|
+
else:
|
260
|
+
write_image(version, boardtype, 'yocto', internal)
|
261
|
+
click.echo("✅ Boot image successfully written.")
|
251
262
|
click.echo("✅ Boot image successfully written.")
|
252
263
|
except Exception as e:
|
253
264
|
click.echo(f"❌ Failed to write boot image: {e}", err=True)
|
@@ -269,10 +280,10 @@ def install_cmd(ctx, component, version):
|
|
269
280
|
Install supported components such as SDKs or tools.
|
270
281
|
|
271
282
|
Examples:
|
272
|
-
cli install palette -v 1.6.0
|
273
|
-
cli install hostdriver -v 1.6.0
|
274
283
|
|
275
|
-
|
284
|
+
sima-cli install hostdriver -v 1.6.0
|
285
|
+
|
286
|
+
sima-cli install optiview
|
276
287
|
"""
|
277
288
|
component = component.lower()
|
278
289
|
internal = ctx.obj.get("internal", False)
|
@@ -295,9 +306,27 @@ def install_cmd(ctx, component, version):
|
|
295
306
|
install_optiview()
|
296
307
|
|
297
308
|
click.echo("✅ Installation complete.")
|
298
|
-
|
299
309
|
|
300
310
|
# ----------------------
|
311
|
+
# Serial Subcommands
|
312
|
+
# ----------------------
|
313
|
+
@main.command(name="serial")
|
314
|
+
@click.option("-b", "--baud", default=115200, show_default=True, help="Baud rate for the serial connection")
|
315
|
+
@click.pass_context
|
316
|
+
def serial_cmd(ctx, baud):
|
317
|
+
"""
|
318
|
+
Connect to the UART serial console of the DevKit.
|
319
|
+
|
320
|
+
Automatically detects the serial port and launches a terminal emulator:
|
321
|
+
|
322
|
+
- macOS: uses 'picocom'
|
323
|
+
|
324
|
+
- Linux: uses 'picocom'
|
325
|
+
|
326
|
+
- Windows: shows PuTTY/Tera Term setup instructions
|
327
|
+
"""
|
328
|
+
connect_serial(ctx, baud)
|
329
|
+
# ----------------------
|
301
330
|
# App Zoo Subcommands
|
302
331
|
# ----------------------
|
303
332
|
# @main.group()
|
sima_cli/install/optiview.py
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
import sys
|
2
2
|
import platform
|
3
3
|
import click
|
4
|
-
import urllib
|
5
4
|
import json
|
6
|
-
import tempfile
|
7
5
|
import tarfile
|
8
6
|
import os
|
9
7
|
import subprocess
|
@@ -31,6 +29,12 @@ def install_optiview():
|
|
31
29
|
version_url = f"{download_url_base}optiview/metadata.json"
|
32
30
|
downloads_dir = Path.home() / "Downloads" / "optiview-installer"
|
33
31
|
downloads_dir.mkdir(parents=True, exist_ok=True)
|
32
|
+
|
33
|
+
# Always redownload the metadata file to get the latest version
|
34
|
+
metadata_path = downloads_dir / "metadata.json"
|
35
|
+
if metadata_path.exists():
|
36
|
+
metadata_path.unlink()
|
37
|
+
|
34
38
|
metadata_path = download_file_from_url(version_url, dest_folder=downloads_dir, internal=False)
|
35
39
|
|
36
40
|
with open(metadata_path, "r") as f:
|
@@ -70,7 +74,8 @@ def install_optiview():
|
|
70
74
|
else:
|
71
75
|
subprocess.run(["bash", os.path.basename(script_path)], check=True, cwd=downloads_dir)
|
72
76
|
|
73
|
-
|
77
|
+
script_name = "run.bat" if platform.system() == "Windows" else "run.sh"
|
78
|
+
click.echo(f"✅ Optiview installed successfully. Run {downloads_dir}/{script_name} to start OptiView")
|
74
79
|
|
75
80
|
except Exception as e:
|
76
81
|
click.echo(f"❌ Installation failed: {e}")
|
File without changes
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import platform
|
2
|
+
import subprocess
|
3
|
+
import shutil
|
4
|
+
import click
|
5
|
+
from sima_cli.utils.env import is_sima_board
|
6
|
+
|
7
|
+
def connect_serial(ctx, baud):
|
8
|
+
"""
|
9
|
+
Connect to the UART serial console of the DevKit.
|
10
|
+
Automatically installs required tools if missing.
|
11
|
+
"""
|
12
|
+
if is_sima_board():
|
13
|
+
click.echo("🚫 This command is not supported on the DevKit. Please run it from your host machine.")
|
14
|
+
ctx.exit(1)
|
15
|
+
|
16
|
+
system = platform.system()
|
17
|
+
internal = ctx.obj.get("internal", False)
|
18
|
+
|
19
|
+
if system == "Darwin":
|
20
|
+
_connect_mac(baud)
|
21
|
+
elif system == "Linux":
|
22
|
+
_connect_linux(baud)
|
23
|
+
elif system == "Windows":
|
24
|
+
_print_windows_instructions()
|
25
|
+
else:
|
26
|
+
click.echo(f"⚠️ Unsupported OS: {system}. Only macOS, Linux, and Windows are supported.")
|
27
|
+
ctx.exit(1)
|
28
|
+
|
29
|
+
click.echo("✅ Serial session ended.")
|
30
|
+
|
31
|
+
|
32
|
+
def _connect_mac(baud):
|
33
|
+
terminal = "picocom"
|
34
|
+
if not shutil.which(terminal):
|
35
|
+
click.echo("⚙️ 'picocom' is not installed. Attempting to install with Homebrew...")
|
36
|
+
if shutil.which("brew"):
|
37
|
+
subprocess.run(["brew", "install", "picocom"], check=True)
|
38
|
+
else:
|
39
|
+
click.echo("❌ Homebrew not found. Please install Homebrew first: https://brew.sh/")
|
40
|
+
raise SystemExit(1)
|
41
|
+
|
42
|
+
ports = sorted(
|
43
|
+
subprocess.getoutput("ls /dev/tty.usbserial-* /dev/cu.usbserial-* 2>/dev/null").splitlines()
|
44
|
+
)
|
45
|
+
if not ports:
|
46
|
+
click.echo("❌ No USB serial device found.")
|
47
|
+
raise SystemExit(1)
|
48
|
+
|
49
|
+
click.echo(f"Connecting to device with picocom ({baud} baud)...")
|
50
|
+
click.echo("🧷 To exit: Press Ctrl + A, then Ctrl + X")
|
51
|
+
click.echo("📜 Scrollback will work in your terminal as expected.\n")
|
52
|
+
|
53
|
+
if not click.confirm("Proceed to connect?", default=True):
|
54
|
+
click.echo("❎ Connection aborted by user.")
|
55
|
+
return
|
56
|
+
|
57
|
+
port = ports[0]
|
58
|
+
click.echo(f"🔌 Connecting to {port} with picocom (115200 8N1)...")
|
59
|
+
try:
|
60
|
+
subprocess.run([
|
61
|
+
terminal,
|
62
|
+
"-b", f"{baud}",
|
63
|
+
"--databits", "8",
|
64
|
+
"--parity", "n",
|
65
|
+
"--stopbits", "1",
|
66
|
+
port
|
67
|
+
])
|
68
|
+
except KeyboardInterrupt:
|
69
|
+
click.echo("\n❎ Serial connection interrupted by user.")
|
70
|
+
|
71
|
+
|
72
|
+
def _connect_linux(baud):
|
73
|
+
terminal = "picocom"
|
74
|
+
if not shutil.which(terminal):
|
75
|
+
click.echo("⚙️ 'picocom' is not installed. Attempting to install via apt...")
|
76
|
+
if shutil.which("apt-get"):
|
77
|
+
subprocess.run(["sudo", "apt-get", "update"], check=True)
|
78
|
+
subprocess.run(["sudo", "apt-get", "install", "-y", "picocom"], check=True)
|
79
|
+
else:
|
80
|
+
click.echo("❌ 'apt-get' not found. Please install picocom manually.")
|
81
|
+
raise SystemExit(1)
|
82
|
+
|
83
|
+
ports = sorted(
|
84
|
+
subprocess.getoutput("ls /dev/ttyUSB* 2>/dev/null").splitlines()
|
85
|
+
)
|
86
|
+
if not ports:
|
87
|
+
click.echo("❌ No USB serial device found.")
|
88
|
+
raise SystemExit(1)
|
89
|
+
|
90
|
+
port = ports[0]
|
91
|
+
click.echo(f"🔌 Connecting to {port} with picocom ({baud} 8N1)...")
|
92
|
+
try:
|
93
|
+
subprocess.run(
|
94
|
+
["sudo", terminal, "-b", f"{baud}", "--databits", "8", "--parity", "n", "--stopbits", "1", port]
|
95
|
+
)
|
96
|
+
except KeyboardInterrupt:
|
97
|
+
click.echo("\n❎ Serial connection interrupted by user.")
|
98
|
+
|
99
|
+
|
100
|
+
def _print_windows_instructions():
|
101
|
+
click.echo("📘 To connect to the DevKit via a serial terminal on Windows, follow these steps:\n")
|
102
|
+
|
103
|
+
click.echo("1. Identify the COM Port:")
|
104
|
+
click.echo(" • Open **Device Manager** → Expand **Ports (COM & LPT)**.")
|
105
|
+
click.echo(" • Look for an entry like **USB Serial Port (COMx)**.\n")
|
106
|
+
|
107
|
+
click.echo("2. Install and Open a Serial Terminal:")
|
108
|
+
click.echo(" • Use **PuTTY** (Download from https://www.putty.org/) or **Tera Term**.")
|
109
|
+
click.echo(" • Set the **Connection Type** to **Serial**.")
|
110
|
+
click.echo(" • Enter the correct **COM Port** (e.g., COM3).")
|
111
|
+
click.echo(" • Set **Baud Rate** to **115200**.")
|
112
|
+
click.echo(" • Click **Open** to start the connection.\n")
|
113
|
+
|
114
|
+
click.echo("🔌 You are now ready to connect to the DevKit over serial.")
|
sima_cli/update/bootimg.py
CHANGED
@@ -4,13 +4,14 @@ import subprocess
|
|
4
4
|
import sys
|
5
5
|
import os
|
6
6
|
import select
|
7
|
-
import time
|
8
7
|
import re
|
8
|
+
import threading
|
9
|
+
from tftpy import TftpServer
|
10
|
+
|
9
11
|
try:
|
10
12
|
from tqdm import tqdm
|
11
13
|
except ImportError:
|
12
14
|
tqdm = None
|
13
|
-
from sima_cli.update.updater import download_image
|
14
15
|
|
15
16
|
|
16
17
|
def list_removable_devices():
|
@@ -177,7 +178,7 @@ def copy_image_to_device(image_path, device_path):
|
|
177
178
|
|
178
179
|
# Regex to parse dd progress (more robust to handle variations)
|
179
180
|
pattern = re.compile(
|
180
|
-
r"(?:
|
181
|
+
r"(?:dd:\s*)?(?P<bytes>\d+)\s+bytes(?:\s+\(.*?\))?\s+(?:transferred|copied),?\s+[\d\.]+\s*s?,?\s+[\d\.]+\s+MB/s",
|
181
182
|
re.IGNORECASE
|
182
183
|
)
|
183
184
|
|
@@ -330,7 +331,6 @@ def write_image(version: str, board: str, swtype: str, internal: bool = False):
|
|
330
331
|
except Exception as e:
|
331
332
|
raise RuntimeError(f"❌ Failed to write image: {e}")
|
332
333
|
|
333
|
-
|
334
334
|
if __name__ == "__main__":
|
335
335
|
if len(sys.argv) != 2:
|
336
336
|
click.echo("❌ Usage: python write_bootimg.py <image_file>")
|
@@ -0,0 +1,408 @@
|
|
1
|
+
from sima_cli.update.updater import download_image
|
2
|
+
from sima_cli.utils.net import get_local_ip_candidates
|
3
|
+
from sima_cli.update.remote import get_remote_board_info, copy_file_to_remote_board, DEFAULT_PASSWORD, run_remote_command, init_ssh_session
|
4
|
+
import os
|
5
|
+
import platform
|
6
|
+
import threading
|
7
|
+
import socket
|
8
|
+
import select
|
9
|
+
import time
|
10
|
+
import logging
|
11
|
+
import click
|
12
|
+
from errno import EINTR
|
13
|
+
from tftpy import TftpServer, TftpException, TftpTimeout, TftpTimeoutExpectACK, DEF_TFTP_PORT, DEF_TIMEOUT_RETRIES
|
14
|
+
from tftpy.TftpContexts import TftpContextServer
|
15
|
+
from tftpy.TftpPacketFactory import TftpPacketFactory
|
16
|
+
|
17
|
+
# Configuration constants
|
18
|
+
MAX_BLKSIZE = 1468 # Block size for MTU compatibility
|
19
|
+
SOCK_TIMEOUT = 2 # Timeout for faster retransmits
|
20
|
+
|
21
|
+
log = logging.getLogger("tftpy.InteractiveTftpServer")
|
22
|
+
emmc_image_paths = []
|
23
|
+
|
24
|
+
def flash_emmc(client_manager, emmc_image_paths):
|
25
|
+
"""Flash eMMC on a selected client device."""
|
26
|
+
clients = [
|
27
|
+
(ip, info) for ip, info in client_manager.get_client_info()
|
28
|
+
if info.get("state") == "Connected"
|
29
|
+
]
|
30
|
+
|
31
|
+
if not clients:
|
32
|
+
click.echo("📭 No connected clients available to flash.")
|
33
|
+
return
|
34
|
+
|
35
|
+
if len(clients) == 1:
|
36
|
+
selected_ip = clients[0][0]
|
37
|
+
else:
|
38
|
+
click.echo("👥 Multiple connected clients found. Select one to flash:")
|
39
|
+
for idx, (ip, info) in enumerate(clients, 1):
|
40
|
+
board_info = info.get("board_info") or "Unknown"
|
41
|
+
click.echo(f" {idx}. {ip} - {board_info}")
|
42
|
+
while True:
|
43
|
+
choice = input("Enter the number of the client to flash: ").strip()
|
44
|
+
if choice.isdigit() and 1 <= int(choice) <= len(clients):
|
45
|
+
selected_ip = clients[int(choice) - 1][0]
|
46
|
+
break
|
47
|
+
click.echo("❌ Invalid choice. Try again.")
|
48
|
+
|
49
|
+
click.echo(f"📡 Selected client: {selected_ip}")
|
50
|
+
remote_dir = "/tmp"
|
51
|
+
|
52
|
+
for path in emmc_image_paths:
|
53
|
+
click.echo(f"📤 Copying {path} to {selected_ip}:{remote_dir}")
|
54
|
+
success = copy_file_to_remote_board(selected_ip, path, remote_dir, passwd=DEFAULT_PASSWORD)
|
55
|
+
if not success:
|
56
|
+
click.echo(f"❌ Failed to copy {path} to {selected_ip}. Aborting.")
|
57
|
+
return
|
58
|
+
|
59
|
+
try:
|
60
|
+
ssh = init_ssh_session(selected_ip, password=DEFAULT_PASSWORD)
|
61
|
+
|
62
|
+
# Step a: Check if eMMC exists
|
63
|
+
check_cmd = "[ -e /dev/mmcblk0p1 ] || (echo '❌ /dev/mmcblk0p1 not found'; exit 1)"
|
64
|
+
run_remote_command(ssh, check_cmd)
|
65
|
+
|
66
|
+
# Step b: Flash with bmaptool
|
67
|
+
wic_path = next((p for p in emmc_image_paths if p.endswith(".wic.gz")), None)
|
68
|
+
if not wic_path:
|
69
|
+
click.echo("❌ No .wic.gz image found in emmc_image_paths.")
|
70
|
+
return
|
71
|
+
|
72
|
+
# Step c: umount the emmc
|
73
|
+
pre_unmount_cmd = (
|
74
|
+
"sudo mount | grep mmcblk0 | awk '{print $3}' | while read mnt; do "
|
75
|
+
"sudo umount \"$mnt\"; done"
|
76
|
+
)
|
77
|
+
run_remote_command(ssh, pre_unmount_cmd)
|
78
|
+
|
79
|
+
# Step d: bmaptool copy the image
|
80
|
+
filename = os.path.basename(wic_path)
|
81
|
+
remote_path = f"/tmp/{filename}"
|
82
|
+
flash_cmd = f"sudo bmaptool copy {remote_path} /dev/mmcblk0"
|
83
|
+
run_remote_command(ssh, flash_cmd)
|
84
|
+
|
85
|
+
# Step d: Fix GPT
|
86
|
+
fix_cmd = 'sudo printf "fix\n" | sudo parted ---pretend-input-tty /dev/mmcblk0 print'
|
87
|
+
run_remote_command(ssh, fix_cmd)
|
88
|
+
|
89
|
+
click.echo("✅ Flash completed. Please reboot the board to boot from eMMC.")
|
90
|
+
except Exception as e:
|
91
|
+
click.echo(f"❌ Flashing failed: {e}")
|
92
|
+
|
93
|
+
class ClientManager:
|
94
|
+
"""Manages TFTP client state and monitoring."""
|
95
|
+
def __init__(self):
|
96
|
+
self.clients = {}
|
97
|
+
self.lock = threading.Lock()
|
98
|
+
self.shutdown_event = threading.Event()
|
99
|
+
|
100
|
+
def add_client(self, ip, filename):
|
101
|
+
"""Add a new client with initial state."""
|
102
|
+
with self.lock:
|
103
|
+
if ip not in self.clients:
|
104
|
+
start_time = time.time()
|
105
|
+
self.clients[ip] = {
|
106
|
+
'state': 'Booting',
|
107
|
+
'filename': filename,
|
108
|
+
'timestamp': start_time,
|
109
|
+
'board_info': None
|
110
|
+
}
|
111
|
+
click.echo(f"📥 New client connected: {ip}")
|
112
|
+
if filename:
|
113
|
+
click.echo(f"📄 Client {ip} requested file: {filename}")
|
114
|
+
# Start monitoring thread
|
115
|
+
threading.Thread(
|
116
|
+
target=self.monitor_client,
|
117
|
+
args=(ip, start_time),
|
118
|
+
daemon=True
|
119
|
+
).start()
|
120
|
+
|
121
|
+
def monitor_client(self, ip, start_time):
|
122
|
+
"""Monitor client by calling get_remote_board_info after 1 minutes, retry every 10 seconds."""
|
123
|
+
try:
|
124
|
+
# Wait ~1 minute before first attempt
|
125
|
+
if self.shutdown_event.wait(timeout=max(0, 60 - (time.time() - start_time))):
|
126
|
+
return
|
127
|
+
|
128
|
+
while not self.shutdown_event.is_set():
|
129
|
+
try:
|
130
|
+
board_type, build_version = get_remote_board_info(ip)
|
131
|
+
|
132
|
+
if board_type and build_version:
|
133
|
+
with self.lock:
|
134
|
+
self.clients[ip]['state'] = 'Connected'
|
135
|
+
self.clients[ip]['board_info'] = f"{board_type}|{build_version}"
|
136
|
+
click.echo(f"✅ Board info retrieved for {ip}: {board_type} with {build_version}")
|
137
|
+
break
|
138
|
+
else:
|
139
|
+
log.debug(f"Incomplete board info for {ip}: {board_type}, {build_version}")
|
140
|
+
|
141
|
+
except Exception as e:
|
142
|
+
log.info(f"get_remote_board_info failed for {ip}, retrying in 10s: {e}")
|
143
|
+
|
144
|
+
if self.shutdown_event.wait(timeout=10):
|
145
|
+
break
|
146
|
+
|
147
|
+
except Exception as e:
|
148
|
+
log.error(f"Unexpected error while monitoring {ip}: {e}")
|
149
|
+
|
150
|
+
def get_client_info(self):
|
151
|
+
"""Return sorted client information for display."""
|
152
|
+
with self.lock:
|
153
|
+
return sorted(self.clients.items(), key=lambda x: x[0])
|
154
|
+
|
155
|
+
def shutdown(self):
|
156
|
+
"""Signal monitoring threads to exit."""
|
157
|
+
self.shutdown_event.set()
|
158
|
+
|
159
|
+
class InteractiveTftpServer(TftpServer):
|
160
|
+
"""Custom TFTP server with client logging and monitoring."""
|
161
|
+
def __init__(self, tftproot, client_manager):
|
162
|
+
super().__init__(tftproot)
|
163
|
+
self.client_manager = client_manager
|
164
|
+
|
165
|
+
def listen(self, listenip="", listenport=DEF_TFTP_PORT, timeout=SOCK_TIMEOUT, retries=DEF_TIMEOUT_RETRIES):
|
166
|
+
"""Override listen to log client IPs and filenames."""
|
167
|
+
tftp_factory = TftpPacketFactory()
|
168
|
+
if not listenip:
|
169
|
+
listenip = "0.0.0.0"
|
170
|
+
log.info(f"Server requested on ip {listenip}, port {listenport}")
|
171
|
+
try:
|
172
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
173
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
174
|
+
self.sock.bind((listenip, listenport))
|
175
|
+
self.sock.setblocking(0)
|
176
|
+
_, self.listenport = self.sock.getsockname()
|
177
|
+
except OSError as err:
|
178
|
+
raise err
|
179
|
+
|
180
|
+
self.is_running.set()
|
181
|
+
log.info("Starting receive loop...")
|
182
|
+
while True:
|
183
|
+
log.debug("shutdown_immediately is %s" % self.shutdown_immediately)
|
184
|
+
log.debug("shutdown_gracefully is %s" % self.shutdown_gracefully)
|
185
|
+
if self.shutdown_immediately:
|
186
|
+
log.info("Shutting down now. Session count: %d" % len(self.sessions))
|
187
|
+
self.sock.close()
|
188
|
+
for key in self.sessions:
|
189
|
+
log.warning("Forcefully closed session with %s" % self.sessions[key].host)
|
190
|
+
self.sessions[key].end()
|
191
|
+
self.sessions = []
|
192
|
+
self.is_running.clear()
|
193
|
+
self.shutdown_gracefully = self.shutdown_immediately = False
|
194
|
+
self.client_manager.shutdown()
|
195
|
+
break
|
196
|
+
elif self.shutdown_gracefully:
|
197
|
+
if not self.sessions:
|
198
|
+
log.info("In graceful shutdown mode and all sessions complete.")
|
199
|
+
self.sock.close()
|
200
|
+
self.is_running.clear()
|
201
|
+
self.shutdown_gracefully = self.shutdown_immediately = False
|
202
|
+
self.client_manager.shutdown()
|
203
|
+
break
|
204
|
+
|
205
|
+
inputlist = [self.sock]
|
206
|
+
for key in self.sessions:
|
207
|
+
inputlist.append(self.sessions[key].sock)
|
208
|
+
|
209
|
+
try:
|
210
|
+
readyinput, _, _ = select.select(inputlist, [], [], timeout)
|
211
|
+
except OSError as err:
|
212
|
+
if err.errno == EINTR:
|
213
|
+
log.debug("Interrupted syscall, retrying")
|
214
|
+
continue
|
215
|
+
else:
|
216
|
+
raise
|
217
|
+
|
218
|
+
deletion_list = []
|
219
|
+
for readysock in readyinput:
|
220
|
+
if readysock == self.sock:
|
221
|
+
log.debug("Data ready on our main socket")
|
222
|
+
buffer, (raddress, rport) = self.sock.recvfrom(MAX_BLKSIZE)
|
223
|
+
log.debug("Read %d bytes", len(buffer))
|
224
|
+
|
225
|
+
if self.shutdown_gracefully:
|
226
|
+
log.warning("Discarding data on main port, in graceful shutdown mode")
|
227
|
+
continue
|
228
|
+
|
229
|
+
key = f"{raddress}:{rport}"
|
230
|
+
if key not in self.sessions:
|
231
|
+
log.debug("Creating new server context for session key = %s" % key)
|
232
|
+
filename = None
|
233
|
+
if buffer[:2] == b'\x00\x01': # RRQ packet
|
234
|
+
filename = buffer[2:].split(b'\x00')[0].decode()
|
235
|
+
self.client_manager.add_client(raddress, filename)
|
236
|
+
self.sessions[key] = TftpContextServer(
|
237
|
+
raddress,
|
238
|
+
rport,
|
239
|
+
timeout,
|
240
|
+
self.root,
|
241
|
+
self.dyn_file_func,
|
242
|
+
self.upload_open,
|
243
|
+
retries=retries
|
244
|
+
)
|
245
|
+
try:
|
246
|
+
self.sessions[key].start(buffer)
|
247
|
+
except TftpTimeoutExpectACK:
|
248
|
+
self.sessions[key].timeout_expectACK = True
|
249
|
+
except TftpException as err:
|
250
|
+
deletion_list.append(key)
|
251
|
+
log.error("Fatal exception thrown from session %s: %s" % (key, str(err)))
|
252
|
+
else:
|
253
|
+
log.warning("received traffic on main socket for existing session??")
|
254
|
+
log.info("Currently handling these sessions:")
|
255
|
+
for session_key, session in list(self.sessions.items()):
|
256
|
+
log.info(" %s" % session)
|
257
|
+
else:
|
258
|
+
for key in self.sessions:
|
259
|
+
if readysock == self.sessions[key].sock:
|
260
|
+
log.debug("Matched input to session key %s" % key)
|
261
|
+
self.sessions[key].timeout_expectACK = False
|
262
|
+
try:
|
263
|
+
self.sessions[key].cycle()
|
264
|
+
if self.sessions[key].state is None:
|
265
|
+
log.info("Successful transfer.")
|
266
|
+
deletion_list.append(key)
|
267
|
+
except TftpTimeoutExpectACK:
|
268
|
+
self.sessions[key].timeout_expectACK = True
|
269
|
+
except TftpException as err:
|
270
|
+
deletion_list.append(key)
|
271
|
+
log.error("Fatal exception thrown from session %s: %s" % (key, str(err)))
|
272
|
+
break
|
273
|
+
else:
|
274
|
+
log.error("Can't find the owner for this packet. Discarding.")
|
275
|
+
|
276
|
+
now = time.time()
|
277
|
+
for key in self.sessions:
|
278
|
+
try:
|
279
|
+
self.sessions[key].checkTimeout(now)
|
280
|
+
except TftpTimeout as err:
|
281
|
+
log.error(str(err))
|
282
|
+
self.sessions[key].retry_count += 1
|
283
|
+
if self.sessions[key].retry_count >= self.sessions[key].retries:
|
284
|
+
log.debug("hit max retries on %s, giving up" % self.sessions[key])
|
285
|
+
deletion_list.append(key)
|
286
|
+
else:
|
287
|
+
log.debug("resending on session %s" % self.sessions[key])
|
288
|
+
self.sessions[key].state.resendLast()
|
289
|
+
|
290
|
+
for key in deletion_list:
|
291
|
+
log.info("Session %s complete" % key)
|
292
|
+
if key in self.sessions:
|
293
|
+
log.debug("Gathering up metrics from session before deleting")
|
294
|
+
self.sessions[key].end()
|
295
|
+
metrics = self.sessions[key].metrics
|
296
|
+
if metrics.duration == 0:
|
297
|
+
log.info("Duration too short, rate undetermined")
|
298
|
+
else:
|
299
|
+
log.info("Transferred %d bytes in %.2f seconds" % (metrics.bytes, metrics.duration))
|
300
|
+
log.info("Average rate: %.2f kbps" % metrics.kbps)
|
301
|
+
click.echo(f"✅ Transfer to {self.sessions[key].host} complete: "
|
302
|
+
f"{metrics.bytes} bytes in {metrics.duration:.2f} s ({metrics.kbps:.2f} kbps)")
|
303
|
+
log.info("%.2f bytes in resent data" % metrics.resent_bytes)
|
304
|
+
log.info("%d duplicate packets" % metrics.dupcount)
|
305
|
+
log.debug("Deleting session %s" % key)
|
306
|
+
del self.sessions[key]
|
307
|
+
log.debug("Session list is now %s" % self.sessions)
|
308
|
+
else:
|
309
|
+
log.warning("Strange, session %s is not on the deletion list" % key)
|
310
|
+
|
311
|
+
self.is_running.clear()
|
312
|
+
self.shutdown_gracefully = self.shutdown_immediately = False
|
313
|
+
self.client_manager.shutdown()
|
314
|
+
|
315
|
+
def run_cli(client_manager):
|
316
|
+
"""Run the interactive CLI for netboot commands."""
|
317
|
+
click.echo("\n🛠 Type 'c' to see connected IPs and board info, 'f' to flash eMMC, or 'q' to quit.\n")
|
318
|
+
while True:
|
319
|
+
try:
|
320
|
+
user_input = input("netboot> ").strip().lower()
|
321
|
+
if user_input in {"q", "quit", "exit"}:
|
322
|
+
click.echo("🛑 Shutting down TFTP server.")
|
323
|
+
return True
|
324
|
+
elif user_input == "c":
|
325
|
+
client_info = client_manager.get_client_info()
|
326
|
+
if client_info:
|
327
|
+
click.echo("🧾 TFTP client IPs and status:")
|
328
|
+
for ip, info in client_info:
|
329
|
+
state = info['state']
|
330
|
+
filename = info['filename'] or "Unknown"
|
331
|
+
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(info['timestamp']))
|
332
|
+
board_info = info['board_info']
|
333
|
+
click.echo(f" • {ip}: {state}, Initial File: {filename}, First seen: {timestamp}")
|
334
|
+
if board_info:
|
335
|
+
click.echo(f" Board Info: {board_info}")
|
336
|
+
else:
|
337
|
+
click.echo("📭 No TFTP client requests received yet.")
|
338
|
+
elif user_input == "f":
|
339
|
+
click.echo("🔧 Initiating eMMC flash (implementation pending).")
|
340
|
+
flash_emmc(client_manager, emmc_image_paths)
|
341
|
+
elif user_input == "":
|
342
|
+
continue
|
343
|
+
else:
|
344
|
+
click.echo("❓ Unknown command. Try 'c' to print client list, 'f' to flash emmc, or 'q'.")
|
345
|
+
except (KeyboardInterrupt, EOFError):
|
346
|
+
click.echo("\n🛑 Exiting netboot session.")
|
347
|
+
return True
|
348
|
+
|
349
|
+
def setup_netboot(version: str, board: str, internal: bool = False, autoflash: bool = False):
|
350
|
+
"""
|
351
|
+
Download and serve a bootable image for network boot over TFTP with client monitoring.
|
352
|
+
|
353
|
+
Parameters:
|
354
|
+
version (str): Firmware version to download (e.g., "1.6.0").
|
355
|
+
board (str): Target board type, e.g., "modalix" or "davinci".
|
356
|
+
internal (bool): Whether to use internal download sources. Defaults to False.
|
357
|
+
autoflash (bool): Whether to automatically flash the devkit when networked booted. Defaults to False.
|
358
|
+
|
359
|
+
Raises:
|
360
|
+
RuntimeError: If the download or TFTP setup fails.
|
361
|
+
"""
|
362
|
+
global emmc_image_paths
|
363
|
+
|
364
|
+
if platform.system() == "Windows":
|
365
|
+
click.echo("❌ Netboot with built-in TFTP is not supported on Windows. Use macOS or Linux.")
|
366
|
+
exit(1)
|
367
|
+
|
368
|
+
try:
|
369
|
+
click.echo(f"⬇️ Downloading netboot image for version: {version}, board: {board}")
|
370
|
+
file_list = download_image(version, board, swtype="yocto", internal=internal, update_type='netboot')
|
371
|
+
if not isinstance(file_list, list):
|
372
|
+
raise ValueError("Expected list of extracted files, got something else.")
|
373
|
+
extract_dir = os.path.dirname(file_list[0])
|
374
|
+
click.echo(f"📁 Image extracted to: {extract_dir}")
|
375
|
+
# Extract specific image paths
|
376
|
+
wic_gz_file = next((f for f in file_list if f.endswith(".wic.gz")), None)
|
377
|
+
bmap_file = next((f for f in file_list if f.endswith(".wic.bmap")), None)
|
378
|
+
emmc_image_paths = [p for p in [wic_gz_file, bmap_file] if p]
|
379
|
+
click.echo(f"📁 eMMC image paths are: {emmc_image_paths}")
|
380
|
+
|
381
|
+
except Exception as e:
|
382
|
+
raise RuntimeError(f"❌ Failed to download and extract netboot image: {e}")
|
383
|
+
|
384
|
+
try:
|
385
|
+
click.echo(f"🚀 Starting TFTP server in: {extract_dir}")
|
386
|
+
ip_candidates = get_local_ip_candidates()
|
387
|
+
if not ip_candidates:
|
388
|
+
click.echo("❌ No suitable local IP addresses found.")
|
389
|
+
exit(1)
|
390
|
+
|
391
|
+
click.echo("🌐 TFTP server is listening on these interfaces (UDP port 69):")
|
392
|
+
for iface, ip in ip_candidates:
|
393
|
+
click.echo(f" 🔹 {iface}: {ip}")
|
394
|
+
|
395
|
+
client_manager = ClientManager()
|
396
|
+
|
397
|
+
server = InteractiveTftpServer(tftproot=extract_dir, client_manager=client_manager)
|
398
|
+
server_thread = threading.Thread(target=server.listen, args=('0.0.0.0', 69), daemon=True)
|
399
|
+
server_thread.start()
|
400
|
+
|
401
|
+
if run_cli(client_manager):
|
402
|
+
server.stop(now=True)
|
403
|
+
client_manager.shutdown()
|
404
|
+
|
405
|
+
except PermissionError:
|
406
|
+
raise RuntimeError("❌ Permission denied. You must run this command with sudo to bind to port 69.")
|
407
|
+
except OSError as e:
|
408
|
+
raise RuntimeError(f"❌ Failed to start TFTP server: {e}")
|
sima_cli/update/remote.py
CHANGED
@@ -84,7 +84,7 @@ def get_remote_board_info(ip: str, passwd: str = DEFAULT_PASSWORD) -> Tuple[str,
|
|
84
84
|
return board_type, build_version
|
85
85
|
|
86
86
|
except Exception as e:
|
87
|
-
click.echo(f"Unable to retrieve board info {e}")
|
87
|
+
click.echo(f"Unable to retrieve board info with error: {e}, board may be still booting.")
|
88
88
|
return "", ""
|
89
89
|
|
90
90
|
|
@@ -95,7 +95,7 @@ def _scp_file(sftp, local_path: str, remote_path: str):
|
|
95
95
|
sftp.put(local_path, remote_path)
|
96
96
|
click.echo("✅ Upload complete")
|
97
97
|
|
98
|
-
def
|
98
|
+
def run_remote_command(ssh, command: str, password: str = DEFAULT_PASSWORD):
|
99
99
|
"""
|
100
100
|
Run a remote command over SSH and stream its output live to the console.
|
101
101
|
If the command starts with 'sudo', pipe in the password.
|
@@ -140,6 +140,13 @@ def _run_remote_command(ssh, command: str, password: str = DEFAULT_PASSWORD):
|
|
140
140
|
for line in remaining_err.splitlines():
|
141
141
|
click.echo(f"⚠️ {line}")
|
142
142
|
|
143
|
+
def init_ssh_session(ip: str, password: str = DEFAULT_PASSWORD):
|
144
|
+
ssh = paramiko.SSHClient()
|
145
|
+
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
146
|
+
|
147
|
+
ssh.connect(ip, username=DEFAULT_USER, password=password, timeout=10)
|
148
|
+
return ssh
|
149
|
+
|
143
150
|
def reboot_remote_board(ip: str, passwd: str):
|
144
151
|
"""
|
145
152
|
Reboot remote board by sending SSH command
|
@@ -150,12 +157,36 @@ def reboot_remote_board(ip: str, passwd: str):
|
|
150
157
|
|
151
158
|
ssh.connect(ip, username=DEFAULT_USER, password=passwd, timeout=10)
|
152
159
|
|
153
|
-
|
154
|
-
|
160
|
+
run_remote_command(ssh, "sudo systemctl stop watchdog", password=passwd)
|
161
|
+
run_remote_command(ssh, "sudo bash -c 'echo b > /proc/sysrq-trigger'", password=passwd)
|
155
162
|
|
156
163
|
except Exception as reboot_err:
|
157
164
|
click.echo(f"⚠️ Unable to connect to the remote board")
|
158
165
|
|
166
|
+
|
167
|
+
def copy_file_to_remote_board(ip: str, file_path: str, remote_dir: str, passwd: str):
|
168
|
+
"""
|
169
|
+
Copy a file to the remote board over SSH.
|
170
|
+
Assumes default credentials: sima / edgeai.
|
171
|
+
"""
|
172
|
+
ssh = paramiko.SSHClient()
|
173
|
+
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
174
|
+
|
175
|
+
try:
|
176
|
+
ssh.connect(ip, username=DEFAULT_USER, password=passwd, timeout=10)
|
177
|
+
sftp = ssh.open_sftp()
|
178
|
+
|
179
|
+
# Upload the file
|
180
|
+
base_file_path = os.path.basename(file_path)
|
181
|
+
click.echo(f"📤 Uploading {file_path} → {remote_dir}")
|
182
|
+
sftp.put(file_path, os.path.join(remote_dir, base_file_path))
|
183
|
+
return True
|
184
|
+
|
185
|
+
except Exception as e:
|
186
|
+
click.echo(f"❌ Remote file copy failed: {e}")
|
187
|
+
|
188
|
+
return False
|
189
|
+
|
159
190
|
def push_and_update_remote_board(ip: str, troot_path: str, palette_path: str, passwd: str, reboot_and_wait: bool):
|
160
191
|
"""
|
161
192
|
Upload and install firmware images to remote board over SSH.
|
@@ -177,7 +208,7 @@ def push_and_update_remote_board(ip: str, troot_path: str, palette_path: str, pa
|
|
177
208
|
click.echo("🚀 Uploaded tRoot image.")
|
178
209
|
|
179
210
|
# Run tRoot update
|
180
|
-
|
211
|
+
run_remote_command(
|
181
212
|
ssh,
|
182
213
|
f"sudo swupdate -H simaai-image-troot:1.0 -i /tmp/{troot_name}", password=passwd
|
183
214
|
)
|
@@ -192,7 +223,7 @@ def push_and_update_remote_board(ip: str, troot_path: str, palette_path: str, pa
|
|
192
223
|
click.echo("🚀 Uploaded system image.")
|
193
224
|
|
194
225
|
# Run Palette update
|
195
|
-
|
226
|
+
run_remote_command(
|
196
227
|
ssh,
|
197
228
|
f"sudo swupdate -H simaai-image-palette:1.0 -i /tmp/{palette_name}",
|
198
229
|
password=passwd
|
@@ -206,11 +237,12 @@ def push_and_update_remote_board(ip: str, troot_path: str, palette_path: str, pa
|
|
206
237
|
click.echo("🔁 Rebooting board after update. Waiting for reconnection...")
|
207
238
|
|
208
239
|
try:
|
209
|
-
|
210
|
-
|
240
|
+
run_remote_command(ssh, "sudo systemctl stop watchdog", password=passwd)
|
241
|
+
run_remote_command(ssh, "sudo bash -c 'echo b > /proc/sysrq-trigger'", password=passwd)
|
211
242
|
|
212
243
|
except Exception as reboot_err:
|
213
244
|
click.echo(f"⚠️ SSH connection lost due to reboot (expected): {reboot_err}, please powercycle the board...")
|
245
|
+
click.confirm("⚠️ Have you powercycled the board?", default=True, abort=True)
|
214
246
|
|
215
247
|
try:
|
216
248
|
ssh.close()
|
@@ -229,7 +261,7 @@ def push_and_update_remote_board(ip: str, troot_path: str, palette_path: str, pa
|
|
229
261
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
230
262
|
ssh.connect(ip, username=DEFAULT_USER, password=passwd, timeout=10)
|
231
263
|
|
232
|
-
|
264
|
+
run_remote_command(ssh, "grep SIMA_BUILD_VERSION /etc/build 2>/dev/null || grep SIMA_BUILD_VERSION /etc/buildinfo 2>/dev/null", password=passwd)
|
233
265
|
ssh.close()
|
234
266
|
except Exception as e:
|
235
267
|
click.echo(f"❌ Unable to validate the version: {e}")
|
sima_cli/update/updater.py
CHANGED
@@ -144,7 +144,18 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
|
|
144
144
|
f"simaai-image-palette-{board}.wic.gz",
|
145
145
|
f"simaai-image-palette-{board}.wic.bmap"
|
146
146
|
}
|
147
|
-
|
147
|
+
elif update_type == 'netboot':
|
148
|
+
target_filenames = {
|
149
|
+
"Image",
|
150
|
+
"netboot.scr.uimg",
|
151
|
+
f"{board}-hhhl.dtb",
|
152
|
+
f"{board}-som.dtb",
|
153
|
+
f"{board}-dvt.dtb",
|
154
|
+
f"{board}-hhhl_x16.dtb",
|
155
|
+
f"simaai-image-palette-{board}.wic.gz",
|
156
|
+
f"simaai-image-palette-{board}.wic.bmap",
|
157
|
+
f"simaai-image-palette-{board}.cpio.gz"
|
158
|
+
}
|
148
159
|
extracted_paths = []
|
149
160
|
|
150
161
|
try:
|
sima_cli/utils/net.py
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
import psutil
|
2
|
+
import socket
|
3
|
+
|
4
|
+
def get_local_ip_candidates():
|
5
|
+
"""
|
6
|
+
Return a list of IPv4 addresses on physical interfaces,
|
7
|
+
excluding VPN, loopback, and link-local interfaces.
|
8
|
+
"""
|
9
|
+
vpn_prefixes = ("tun", "tap", "utun", "tailscale", "wg", "docker") # WireGuard, Tailscale, etc.
|
10
|
+
ip_list = []
|
11
|
+
|
12
|
+
for iface_name, iface_addrs in psutil.net_if_addrs().items():
|
13
|
+
# Exclude VPN or tunnel interfaces
|
14
|
+
if iface_name.startswith(vpn_prefixes):
|
15
|
+
continue
|
16
|
+
|
17
|
+
for addr in iface_addrs:
|
18
|
+
if addr.family == socket.AF_INET:
|
19
|
+
ip = addr.address
|
20
|
+
|
21
|
+
# Skip loopback and link-local
|
22
|
+
if ip.startswith("127.") or ip.startswith("169.254."):
|
23
|
+
continue
|
24
|
+
|
25
|
+
ip_list.append((iface_name, ip))
|
26
|
+
|
27
|
+
# Prioritize physical interfaces: eth0, en0, wlan0, etc.
|
28
|
+
ip_list.sort(key=lambda x: (not x[0].startswith(("eth", "en", "wlan", "wl")), x[0]))
|
29
|
+
return ip_list
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sima-cli
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.20
|
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
|
@@ -24,6 +24,8 @@ Requires-Dist: paramiko
|
|
24
24
|
Requires-Dist: plotext
|
25
25
|
Requires-Dist: rich
|
26
26
|
Requires-Dist: InquirerPy
|
27
|
+
Requires-Dist: tftpy
|
28
|
+
Requires-Dist: psutil
|
27
29
|
Dynamic: author
|
28
30
|
Dynamic: license-file
|
29
31
|
Dynamic: requires-python
|
@@ -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=U-M6HF-1PsXkez7yhqMRRsr6FObzLVRQ6Spmo4bkl30,49
|
4
|
+
sima_cli/cli.py,sha256=b4ScjEa5Ltikry4ftZdJaNxBlSMYtmjYlodqzbRyWRk,12658
|
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
|
@@ -13,27 +13,31 @@ sima_cli/download/__init__.py,sha256=6y4O2FOCYFR2jdnQoVi3hRtEoZ0Gw6rydlTy1SGJ5FE
|
|
13
13
|
sima_cli/download/downloader.py,sha256=pHfqcg_ujBQjds_EkcRV85M2mRYGrysoZaiR-FIrpf4,5161
|
14
14
|
sima_cli/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
sima_cli/install/hostdriver.py,sha256=kAWDLebs60mbWIyTbUxmNrChcKW1uD5r7FtWNSUVUE4,5852
|
16
|
-
sima_cli/install/optiview.py,sha256=
|
16
|
+
sima_cli/install/optiview.py,sha256=i5eWVor-9MScEfrQm3Ty9OP4VpSsCgWvNh7AvYdZu7s,3365
|
17
17
|
sima_cli/install/palette.py,sha256=uRznoHa4Mv9ZXHp6AoqknfC3RxpYNKi9Ins756Cyifk,3930
|
18
18
|
sima_cli/mla/meminfo.py,sha256=ndc8kQJmWGEIdvNh6iIhATGdrkqM2pbddr_eHxaPNfg,1466
|
19
19
|
sima_cli/model_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
20
|
sima_cli/model_zoo/model.py,sha256=q91Nrg62j1TqwPO8HiX4nlEFCCmzNEFcyFTBVMbJm8w,9836
|
21
21
|
sima_cli/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
sima_cli/sdk/syscheck.py,sha256=h9zCULW67y4i2hqiGc-hc1ucBDShA5FAe9NxwBGq-fM,4575
|
23
|
+
sima_cli/serial/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
+
sima_cli/serial/serial.py,sha256=6xRta_PzE_DmmooYq35lbK76TYpAny5SEJAdYC_3fH0,4141
|
23
25
|
sima_cli/update/__init__.py,sha256=0P-z-rSaev40IhfJXytK3AFWv2_sdQU4Ry6ei2sEus0,66
|
24
26
|
sima_cli/update/bmaptool.py,sha256=KrhUGShBwY4Wzz50QiuMYAxxPgEy1nz5C68G-0a4qF4,4988
|
25
|
-
sima_cli/update/bootimg.py,sha256=
|
27
|
+
sima_cli/update/bootimg.py,sha256=AOZI9bXrY8x-1gwlbIINdOUNO_naw9qteNJqeQ9_o-Y,13407
|
26
28
|
sima_cli/update/local.py,sha256=CyUFLs5Lz5w4VyM6ip4wndKBBLz3_KZ-3scEvSiOrcg,3299
|
29
|
+
sima_cli/update/netboot.py,sha256=xTtRf8LMuqC_Ye-m6tlv5kbwkZwexc623kRymwiLTf4,18528
|
27
30
|
sima_cli/update/query.py,sha256=cVkUMLZkONJ2XMEwqEC-JqLVB38hOqfWM2hB2ehBK6Y,3272
|
28
|
-
sima_cli/update/remote.py,sha256=
|
29
|
-
sima_cli/update/updater.py,sha256=
|
31
|
+
sima_cli/update/remote.py,sha256=RXQbNCDr7d8wLJ7pdGwA6G3gzgwrfZ9l_7YNYUfGHDU,10067
|
32
|
+
sima_cli/update/updater.py,sha256=1K87YDj_isuMtL38ZI-hwBTIRmk3WqbkE6s-J0IVp-Q,18884
|
30
33
|
sima_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
34
|
sima_cli/utils/artifactory.py,sha256=6YyVpzVm8ATy7NEwT9nkWx-wptkXrvG7Wl_zDT6jmLs,2390
|
32
35
|
sima_cli/utils/config.py,sha256=wE-cPQqY_gOqaP8t01xsRHD9tBUGk9MgBUm2GYYxI3E,1616
|
33
36
|
sima_cli/utils/config_loader.py,sha256=7I5we1yiCai18j9R9jvhfUzAmT3OjAqVK35XSLuUw8c,2005
|
34
37
|
sima_cli/utils/env.py,sha256=Jrb062EnpMBr1jGMotBlI2j9LEH6W1Z5Tgt6LHY7yYQ,5753
|
38
|
+
sima_cli/utils/net.py,sha256=WVntA4CqipkNrrkA4tBVRadJft_pMcGYh4Re5xk3rqo,971
|
35
39
|
sima_cli/utils/network.py,sha256=UvqxbqbWUczGFyO-t1SybG7Q-x9kjUVRNIn_D6APzy8,1252
|
36
|
-
sima_cli-0.0.
|
40
|
+
sima_cli-0.0.20.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
|
37
41
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
42
|
tests/test_app_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
43
|
tests/test_auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -42,8 +46,8 @@ tests/test_download.py,sha256=t87DwxlHs26_ws9rpcHGwr_OrcRPd3hz6Zmm0vRee2U,4465
|
|
42
46
|
tests/test_firmware.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
47
|
tests/test_model_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
48
|
tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
|
-
sima_cli-0.0.
|
46
|
-
sima_cli-0.0.
|
47
|
-
sima_cli-0.0.
|
48
|
-
sima_cli-0.0.
|
49
|
-
sima_cli-0.0.
|
49
|
+
sima_cli-0.0.20.dist-info/METADATA,sha256=0KwVrHOfciP7OQWLcpOhnarYPVPkTcffT4QSKNXqzwo,3674
|
50
|
+
sima_cli-0.0.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
51
|
+
sima_cli-0.0.20.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
|
52
|
+
sima_cli-0.0.20.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
|
53
|
+
sima_cli-0.0.20.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|