silabs-pycommander-core 1.0.0__py3-none-win_amd64.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.
- pycommander_core/__init__.py +25 -0
- pycommander_core/__main__.py +18 -0
- pycommander_core/_ensure_commander.py +114 -0
- pycommander_core/_utils.py +37 -0
- pycommander_core/_version.py +14 -0
- pycommander_core/adapter_base.py +368 -0
- pycommander_core/aemstream_base.py +118 -0
- pycommander_core/cli.py +41 -0
- pycommander_core/commander_base.py +265 -0
- pycommander_core/commands/__init__.py +62 -0
- pycommander_core/commands/_base.py +208 -0
- pycommander_core/commands/adapter.py +206 -0
- pycommander_core/commands/aem.py +140 -0
- pycommander_core/commands/convert.py +108 -0
- pycommander_core/commands/ctune.py +57 -0
- pycommander_core/commands/device.py +151 -0
- pycommander_core/commands/ebl.py +146 -0
- pycommander_core/commands/extflash.py +102 -0
- pycommander_core/commands/flash.py +102 -0
- pycommander_core/commands/gbl3.py +218 -0
- pycommander_core/commands/gbl4.py +151 -0
- pycommander_core/commands/littlefs.py +240 -0
- pycommander_core/commands/mfg917.py +753 -0
- pycommander_core/commands/nvm3.py +264 -0
- pycommander_core/commands/ota.py +199 -0
- pycommander_core/commands/postbuild.py +45 -0
- pycommander_core/commands/readmem.py +52 -0
- pycommander_core/commands/rps.py +197 -0
- pycommander_core/commands/security.py +742 -0
- pycommander_core/commands/serial.py +115 -0
- pycommander_core/commands/tokens.py +170 -0
- pycommander_core/commands/util.py +237 -0
- pycommander_core/commands/vcom.py +51 -0
- pycommander_core/commands/verify.py +84 -0
- pycommander_core/errors.py +24 -0
- pycommander_core/paths.py +28 -0
- pycommander_core/runner.py +191 -0
- pycommander_core/target.py +1019 -0
- pycommander_core/types.py +286 -0
- silabs_pycommander_core-1.0.0.dist-info/METADATA +30 -0
- silabs_pycommander_core-1.0.0.dist-info/RECORD +43 -0
- silabs_pycommander_core-1.0.0.dist-info/WHEEL +5 -0
- silabs_pycommander_core-1.0.0.dist-info/licenses/LICENSE.md +8 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License
|
|
3
|
+
Copyright 2026 Silicon Laboratories Inc. www.silabs.com
|
|
4
|
+
*******************************************************************************
|
|
5
|
+
The licensor of this software is Silicon Laboratories Inc. Your use of this
|
|
6
|
+
software is governed by the terms of Silicon Labs Master Software License
|
|
7
|
+
Agreement (MSLA) available at
|
|
8
|
+
www.silabs.com/about-us/legal/master-software-license-agreement. This
|
|
9
|
+
software is distributed to you in Source Code format and is governed by the
|
|
10
|
+
sections of the MSLA applicable to Source Code.
|
|
11
|
+
*******************************************************************************
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# This is the entry point for the PyCommander package, when you do `import pycommander`
|
|
15
|
+
from .commander_base import CommanderBase
|
|
16
|
+
from .adapter_base import AdapterBase
|
|
17
|
+
from .target import Target
|
|
18
|
+
from ._version import __version__
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CommanderBase",
|
|
22
|
+
"AdapterBase",
|
|
23
|
+
"Target",
|
|
24
|
+
"__version__",
|
|
25
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License
|
|
3
|
+
Copyright 2026 Silicon Laboratories Inc. www.silabs.com
|
|
4
|
+
*******************************************************************************
|
|
5
|
+
The licensor of this software is Silicon Laboratories Inc. Your use of this
|
|
6
|
+
software is governed by the terms of Silicon Labs Master Software License
|
|
7
|
+
Agreement (MSLA) available at
|
|
8
|
+
www.silabs.com/about-us/legal/master-software-license-agreement. This
|
|
9
|
+
software is distributed to you in Source Code format and is governed by the
|
|
10
|
+
sections of the MSLA applicable to Source Code.
|
|
11
|
+
*******************************************************************************
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Entry point for `pycommander` command and `python3 -m pycommander`
|
|
15
|
+
from .cli import main
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License
|
|
3
|
+
Copyright 2026 Silicon Laboratories Inc. www.silabs.com
|
|
4
|
+
*******************************************************************************
|
|
5
|
+
The licensor of this software is Silicon Laboratories Inc. Your use of this
|
|
6
|
+
software is governed by the terms of Silicon Labs Master Software License
|
|
7
|
+
Agreement (MSLA) available at
|
|
8
|
+
www.silabs.com/about-us/legal/master-software-license-agreement. This
|
|
9
|
+
software is distributed to you in Source Code format and is governed by the
|
|
10
|
+
sections of the MSLA applicable to Source Code.
|
|
11
|
+
*******************************************************************************
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Ensure that the Commander executable is present
|
|
15
|
+
# This entails checking if the executable is present where it should be, and if not, extracting it from the
|
|
16
|
+
# archive embedded in the package.
|
|
17
|
+
import sys
|
|
18
|
+
import hashlib
|
|
19
|
+
import subprocess
|
|
20
|
+
import shutil
|
|
21
|
+
|
|
22
|
+
import importlib.resources as ir
|
|
23
|
+
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from platformdirs import user_cache_dir
|
|
26
|
+
|
|
27
|
+
from .paths import EXECUTABLE_PATH_CLI, EXECUTABLE_PATH_GUI
|
|
28
|
+
|
|
29
|
+
def ensure_commander(cli: bool = True) -> Path:
|
|
30
|
+
if cli:
|
|
31
|
+
package_name = "pycommander_cli._archive"
|
|
32
|
+
executable_path = EXECUTABLE_PATH_CLI
|
|
33
|
+
else:
|
|
34
|
+
package_name = "pycommander_gui._archive"
|
|
35
|
+
executable_path = EXECUTABLE_PATH_GUI
|
|
36
|
+
|
|
37
|
+
cache_dir = Path(user_cache_dir(f"pycommander", "silabs"))
|
|
38
|
+
archive_path = Path(_find_executable_archive_resource(package_name))
|
|
39
|
+
archive_hash = _compute_hash_of_file(archive_path)
|
|
40
|
+
|
|
41
|
+
target_dir = cache_dir / archive_hash
|
|
42
|
+
|
|
43
|
+
if not target_dir.exists():
|
|
44
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
_extract_commander(archive_path, target_dir)
|
|
47
|
+
|
|
48
|
+
return Path(target_dir / executable_path)
|
|
49
|
+
|
|
50
|
+
def _extract_commander(zip_file_path: Path, destination: Path) -> None:
|
|
51
|
+
# Remove the existing executable and stamp files, if they exist.
|
|
52
|
+
if destination.exists():
|
|
53
|
+
shutil.rmtree(destination, ignore_errors=True)
|
|
54
|
+
|
|
55
|
+
# Find the filename of the executable in the archive
|
|
56
|
+
with ir.as_file(zip_file_path) as zip_file:
|
|
57
|
+
if sys.platform == "darwin":
|
|
58
|
+
_extract_commander_macos(zip_file, destination)
|
|
59
|
+
elif sys.platform == "linux":
|
|
60
|
+
_extract_commander_linux(zip_file, destination)
|
|
61
|
+
elif sys.platform == "win32":
|
|
62
|
+
_extract_commander_windows(zip_file, destination)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError(f"Unsupported platform: {sys.platform}")
|
|
65
|
+
|
|
66
|
+
def _extract_commander_macos(zip_file: Path, destination: Path) -> None:
|
|
67
|
+
if not zip_file.exists():
|
|
68
|
+
raise FileNotFoundError(f"Executable archive not found: {zip_file}")
|
|
69
|
+
|
|
70
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
subprocess.run(
|
|
72
|
+
["ditto", "-xk", str(zip_file), str(destination)],
|
|
73
|
+
check=True
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def _extract_commander_linux(zip_file: Path, destination: Path) -> None:
|
|
77
|
+
if not zip_file.exists():
|
|
78
|
+
raise FileNotFoundError(f"Executable archive not found: {zip_file}")
|
|
79
|
+
|
|
80
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
subprocess.run(
|
|
82
|
+
["tar", "-xjf", str(zip_file), "-C", str(destination)],
|
|
83
|
+
check=True
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def _extract_commander_windows(zip_file: Path, destination: Path) -> None:
|
|
87
|
+
if not zip_file.exists():
|
|
88
|
+
raise FileNotFoundError(f"Executable archive not found: {zip_file}")
|
|
89
|
+
|
|
90
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
subprocess.run(
|
|
92
|
+
["powershell", "-Command", "Expand-Archive", f"-Path {str(zip_file)}", f"-DestinationPath {str(destination)}"],
|
|
93
|
+
check=True
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def _compute_hash_of_file(path: Path) -> str:
|
|
97
|
+
hash = hashlib.sha256()
|
|
98
|
+
hash.update(path.read_bytes())
|
|
99
|
+
return hash.hexdigest()[:16]
|
|
100
|
+
|
|
101
|
+
def _find_executable_archive_resource(package: str) -> Path:
|
|
102
|
+
root = ir.files(package)
|
|
103
|
+
candidates : list[Path] = []
|
|
104
|
+
for entry in root.iterdir():
|
|
105
|
+
if not entry.is_file():
|
|
106
|
+
continue
|
|
107
|
+
name = entry.name
|
|
108
|
+
if name.endswith(".zip") or name.endswith(".tar.bz"):
|
|
109
|
+
candidates.append(root / name)
|
|
110
|
+
|
|
111
|
+
if len(candidates) == 0:
|
|
112
|
+
raise FileNotFoundError(f"No executable archive found in {root}")
|
|
113
|
+
|
|
114
|
+
return candidates[0]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License
|
|
3
|
+
Copyright 2026 Silicon Laboratories Inc. www.silabs.com
|
|
4
|
+
*******************************************************************************
|
|
5
|
+
The licensor of this software is Silicon Laboratories Inc. Your use of this
|
|
6
|
+
software is governed by the terms of Silicon Labs Master Software License
|
|
7
|
+
Agreement (MSLA) available at
|
|
8
|
+
www.silabs.com/about-us/legal/master-software-license-agreement. This
|
|
9
|
+
software is distributed to you in Source Code format and is governed by the
|
|
10
|
+
sections of the MSLA applicable to Source Code.
|
|
11
|
+
*******************************************************************************
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def sanitize_args(args: list[str | int | float | None]) -> list[str]:
|
|
15
|
+
# NOTE: We do NOT want to split any args that contain whitespace into multiple args.
|
|
16
|
+
# There might be filenames or other arguments that have whitespace in them.
|
|
17
|
+
|
|
18
|
+
sanitized_args : list[str] = []
|
|
19
|
+
for arg in args:
|
|
20
|
+
# Remove any empty or None elements
|
|
21
|
+
if arg is None:
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
# Stringify any non-string elements
|
|
25
|
+
if not isinstance(arg, str):
|
|
26
|
+
arg = str(arg)
|
|
27
|
+
|
|
28
|
+
# Remove any whitespace-only elements
|
|
29
|
+
if arg.strip() == "":
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
# Trim leading and trailing whitespace
|
|
33
|
+
arg = arg.strip()
|
|
34
|
+
|
|
35
|
+
sanitized_args.append(arg)
|
|
36
|
+
|
|
37
|
+
return sanitized_args
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License
|
|
3
|
+
Copyright 2026 Silicon Laboratories Inc. www.silabs.com
|
|
4
|
+
*******************************************************************************
|
|
5
|
+
The licensor of this software is Silicon Laboratories Inc. Your use of this
|
|
6
|
+
software is governed by the terms of Silicon Labs Master Software License
|
|
7
|
+
Agreement (MSLA) available at
|
|
8
|
+
www.silabs.com/about-us/legal/master-software-license-agreement. This
|
|
9
|
+
software is distributed to you in Source Code format and is governed by the
|
|
10
|
+
sections of the MSLA applicable to Source Code.
|
|
11
|
+
*******************************************************************************
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License
|
|
3
|
+
Copyright 2026 Silicon Laboratories Inc. www.silabs.com
|
|
4
|
+
*******************************************************************************
|
|
5
|
+
The licensor of this software is Silicon Laboratories Inc. Your use of this
|
|
6
|
+
software is governed by the terms of Silicon Labs Master Software License
|
|
7
|
+
Agreement (MSLA) available at
|
|
8
|
+
www.silabs.com/about-us/legal/master-software-license-agreement. This
|
|
9
|
+
software is distributed to you in Source Code format and is governed by the
|
|
10
|
+
sections of the MSLA applicable to Source Code.
|
|
11
|
+
*******************************************************************************
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from . import types
|
|
17
|
+
|
|
18
|
+
from .commander_base import CommanderBase
|
|
19
|
+
from .target import Target
|
|
20
|
+
|
|
21
|
+
class AdapterBase:
|
|
22
|
+
"""High-level interface for interacting with a Silicon Labs adapter (kit/debugger).
|
|
23
|
+
|
|
24
|
+
The convenience methods on this class (adapter info, reset, supply voltage, VCOM
|
|
25
|
+
configuration, and energy analysis) rely on features that are only present on
|
|
26
|
+
Silicon Labs adapters, and are not available when using a generic J-Link adapter.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self,
|
|
30
|
+
serial_number: str | None = None,
|
|
31
|
+
ip_address: str | None = None,
|
|
32
|
+
serial_port: str | None = None,
|
|
33
|
+
target_device: str | None = None,
|
|
34
|
+
debug_speed: int | None = None,
|
|
35
|
+
debug_tif: str | None = None,
|
|
36
|
+
debug_irpre: int | None = None,
|
|
37
|
+
debug_drpre: int | None = None,
|
|
38
|
+
target: Target | None = None,
|
|
39
|
+
commander: CommanderBase | None = None):
|
|
40
|
+
|
|
41
|
+
if commander is None:
|
|
42
|
+
raise ValueError("commander must be provided")
|
|
43
|
+
|
|
44
|
+
self._commander : CommanderBase = commander
|
|
45
|
+
self.target : Target | None = target
|
|
46
|
+
|
|
47
|
+
def info(self) -> types.AdapterInfo | None:
|
|
48
|
+
"""Get information about the adapter. Only available on Silicon Labs adapters.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
An AdapterInfo object containing the information about the adapter, or None if the information could not be retrieved.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
result : dict = self._commander.adapter.probe()
|
|
55
|
+
|
|
56
|
+
if not result["success"]:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
if "board_lists" not in result["result"]:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
board_list : list[types.AdapterBoardInfo] = []
|
|
63
|
+
for board in result["result"]["board_lists"]:
|
|
64
|
+
board_list.append(types.AdapterBoardInfo(
|
|
65
|
+
name=board.get("name", None),
|
|
66
|
+
part_number=board.get("part_number", None),
|
|
67
|
+
serial_number=board.get("serial_number", None),
|
|
68
|
+
target_device=board.get("target_device", None),
|
|
69
|
+
))
|
|
70
|
+
|
|
71
|
+
if "firmware_info" not in result["result"]:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
fw_info = types.AdapterFwInfo(
|
|
75
|
+
current_version=result["result"]["firmware_info"].get("fw_version", None),
|
|
76
|
+
latest_version=result["result"]["firmware_info"].get("new_fw_version", None),
|
|
77
|
+
upgrade_available=result["result"]["firmware_info"].get("upgrade_available", None),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if "kit_info" not in result["result"]:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
adapter_info = types.AdapterInfo(
|
|
84
|
+
board_list=board_list,
|
|
85
|
+
fw_info=fw_info,
|
|
86
|
+
jlink_serial_number=result["result"]["kit_info"].get("j_link_serial", None),
|
|
87
|
+
vcom_port=result["result"]["kit_info"].get("vcom_port", None),
|
|
88
|
+
vcom_supported=result["result"]["kit_info"].get("vcom_supported", None),
|
|
89
|
+
ip_supported=result["result"]["kit_info"].get("ip_supported", None),
|
|
90
|
+
ip_address=result["result"]["kit_info"].get("ip_address", None),
|
|
91
|
+
mac_address=result["result"]["kit_info"].get("mac_address", None),
|
|
92
|
+
nickname=result["result"]["kit_info"].get("nickname", None),
|
|
93
|
+
kit_name=result["result"]["kit_info"].get("kit_name", None),
|
|
94
|
+
kit_part_number=result["result"]["kit_info"].get("kit_part_number", None),
|
|
95
|
+
aem_supported=result["result"]["kit_info"].get("aem_supported", None),
|
|
96
|
+
debug_mode=result["result"]["kit_info"].get("debug_mode", None),
|
|
97
|
+
debug_part=result["result"]["kit_info"].get("debug_part", None),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return adapter_info
|
|
101
|
+
|
|
102
|
+
def reset(self) -> bool:
|
|
103
|
+
"""Reset the adapter. Only available on Silicon Labs adapters.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if the adapter was reset successfully, False otherwise.
|
|
107
|
+
"""
|
|
108
|
+
result : dict = self._commander.adapter.reset()
|
|
109
|
+
return result["success"]
|
|
110
|
+
|
|
111
|
+
def upgradeFirmware(self, filename: Path | None = None) -> types.AdapterFwUpgradeResult | None:
|
|
112
|
+
"""Upgrade the firmware of the adapter. Only available on Silicon Labs adapters.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
filename (Path | None): The path to the firmware package to upgrade (*.emz). If not provided, Commander's bundled firmware will be used; in this case, the upgrade will only be performed if the bundled version is newer than the installed version.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
An AdapterFwUpgradeResult object containing the result of the firmware upgrade, or None if the firmware upgrade could not be performed.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
current_version : str | None = self.__get_current_firmware_version()
|
|
122
|
+
|
|
123
|
+
args = []
|
|
124
|
+
if filename is None or str(filename) == "":
|
|
125
|
+
# Check if a firmware upgrade is available first
|
|
126
|
+
result : dict = self._commander.adapter.fwupgradecheck()
|
|
127
|
+
if not result["success"]:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
if not result["result"].get("upgrade_available", False):
|
|
131
|
+
return types.AdapterFwUpgradeResult(
|
|
132
|
+
package_was_installed=False,
|
|
133
|
+
currently_installed_version=current_version,
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
args.append(str(filename))
|
|
137
|
+
|
|
138
|
+
result : dict = self._commander.adapter.fwupgrade(*args)
|
|
139
|
+
if not result["success"]:
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
new_version : str | None = self.__get_current_firmware_version()
|
|
143
|
+
|
|
144
|
+
return types.AdapterFwUpgradeResult(
|
|
145
|
+
package_was_installed=True,
|
|
146
|
+
currently_installed_version=new_version,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def getVoltage(self) -> types.AdapterVoltageInfo | None:
|
|
150
|
+
"""Get the supply voltage for the target device. Only available on Silicon Labs adapters.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
A dictionary of rail indices and AdapterVoltageInfo objects containing the supply voltage
|
|
154
|
+
information for each rail, or None if the supply voltage information could not be retrieved.
|
|
155
|
+
If no supply voltage information is available for a rail, the rail index will not be present in the dictionary.
|
|
156
|
+
"""
|
|
157
|
+
result : dict = self._commander.adapter.voltage()
|
|
158
|
+
|
|
159
|
+
if not result["success"]:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
voltage_info : types.AdapterVoltageInfo = types.AdapterVoltageInfo(rails=[])
|
|
163
|
+
for rail_info in result["result"]["voltages"]:
|
|
164
|
+
voltage_info.rails.append(types.AdapterRailInfo(
|
|
165
|
+
rail_index=rail_info.get("rail_index", None),
|
|
166
|
+
configured_voltage_v=rail_info.get("configured_voltage_v", None),
|
|
167
|
+
measured_voltage_v=rail_info.get("measured_voltage_v", None),
|
|
168
|
+
rail_powered=rail_info.get("rail_powered", None),
|
|
169
|
+
))
|
|
170
|
+
|
|
171
|
+
return voltage_info
|
|
172
|
+
|
|
173
|
+
def setVoltage(self, voltage: float, calibrate: bool = True) -> bool:
|
|
174
|
+
"""Set the supply voltage for the target device. Only available on Silicon Labs adapters.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
voltage (float): Supply voltage to set.
|
|
178
|
+
calibrate (bool): If True, automatically calibrate if voltage has changed.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
True if the supply voltage was set successfully, False otherwise.
|
|
182
|
+
"""
|
|
183
|
+
result : dict = self._commander.adapter.voltage(voltage=voltage, calibrate=calibrate)
|
|
184
|
+
return result["success"]
|
|
185
|
+
|
|
186
|
+
def setVcomConfig(self, baudrate: int, handshake: types.VcomHandshake, store: bool = False) -> bool:
|
|
187
|
+
"""Set the VCOM configuration for the adapter. Only available on Silicon Labs adapters.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
baudrate (int): VCOM baudrate.
|
|
191
|
+
handshake (VcomHandshake): VCOM handshake.
|
|
192
|
+
store (bool): Store the VCOM configuration.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
True if the VCOM configuration was set successfully, False otherwise.
|
|
196
|
+
"""
|
|
197
|
+
if handshake not in types.VcomHandshake.__members__.values():
|
|
198
|
+
raise ValueError(f"Invalid handshake: {handshake}")
|
|
199
|
+
|
|
200
|
+
result : dict = self._commander.vcom.config(baudrate=baudrate, handshake=handshake.value, store=store)
|
|
201
|
+
return result["success"]
|
|
202
|
+
|
|
203
|
+
def analyzeEnergyUsage(self, duration_s: float, get_distribution: bool = False, cluster_states: bool = False, detect_period: bool = False) -> types.AemAnalysisResult | None:
|
|
204
|
+
"""Analyze the energy usage of the target device. Only available on Silicon Labs adapters.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
duration_s: Duration of the measurement in seconds.
|
|
208
|
+
get_distribution: If True, get the distribution of measurements.
|
|
209
|
+
cluster_states: If True, cluster the states of the measurements.
|
|
210
|
+
detect_period: If True, detect the period of the measurements.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
An AemAnalysisResult object containing the analysis results, or None if the analysis could not be performed.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
if not get_distribution and not cluster_states and not detect_period:
|
|
217
|
+
raise ValueError("Distribution, cluster states, and period detection cannot all be False.")
|
|
218
|
+
|
|
219
|
+
result : dict = self._commander.aem.analyze(windowlength_ms=duration_s * 1000, get_distribution=get_distribution, cluster=cluster_states, find_period=detect_period)
|
|
220
|
+
if not result["success"]:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
aem_analysis_result : types.AemAnalysisResult | None = AdapterBase.__parse_aem_analyze_output(result)
|
|
224
|
+
return aem_analysis_result
|
|
225
|
+
|
|
226
|
+
#########################################################
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def __parse_aem_analyze_output(result: dict) -> types.AemAnalysisResult | None:
|
|
230
|
+
if "result" not in result:
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
aem_distribution : types.AemDistribution | None = None
|
|
234
|
+
aem_clustering : types.AemClustering | None = None
|
|
235
|
+
aem_period_detection : types.AemPeriodDetection | None = None
|
|
236
|
+
aem_signal_characteristics : types.AemSignalCharacteristics | None = None
|
|
237
|
+
|
|
238
|
+
dist_dict : dict | None = result["result"].get("distribution", None)
|
|
239
|
+
if dist_dict:
|
|
240
|
+
aem_distribution = types.AemDistribution()
|
|
241
|
+
aem_distribution.bins = []
|
|
242
|
+
for bin in dist_dict.get("bins", {}):
|
|
243
|
+
aem_distribution.bins.append(types.AemDistributionBin(
|
|
244
|
+
average_current = bin.get("average_current", None),
|
|
245
|
+
bin_max = bin.get("bin_max", None),
|
|
246
|
+
bin_min = bin.get("bin_min", None),
|
|
247
|
+
current_unit = bin.get("current_unit", None),
|
|
248
|
+
num_samples = bin.get("num_samples", None),
|
|
249
|
+
percentage = bin.get("percentage", None),
|
|
250
|
+
standard_deviation = bin.get("standard_deviation", None),
|
|
251
|
+
time = bin.get("time", None),
|
|
252
|
+
time_unit = bin.get("time_unit", None),
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
aem_distribution.configuration = types.AemDistributionConfiguration(
|
|
256
|
+
bins = dist_dict.get("configuration", {}).get("bins", None),
|
|
257
|
+
logarithmic = dist_dict.get("configuration", {}).get("logarithmic", None),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
aem_distribution.summary = types.AemDistributionSummary(
|
|
261
|
+
max_current = dist_dict.get("summary", {}).get("max_current", None),
|
|
262
|
+
min_current = dist_dict.get("summary", {}).get("min_current", None),
|
|
263
|
+
total_duration_ms = dist_dict.get("summary", {}).get("total_duration_ms", None),
|
|
264
|
+
total_samples = dist_dict.get("summary", {}).get("total_samples", None),
|
|
265
|
+
unit = dist_dict.get("summary", {}).get("unit", None),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
aem_distribution.type = dist_dict.get("type", None)
|
|
269
|
+
|
|
270
|
+
cluster_dict : dict | None = result["result"].get("clustering", None)
|
|
271
|
+
if cluster_dict:
|
|
272
|
+
aem_clustering = types.AemClustering()
|
|
273
|
+
aem_clustering.blocks = []
|
|
274
|
+
for block in cluster_dict.get("blocks", {}):
|
|
275
|
+
aem_clustering.blocks.append(types.AemClusterBlock(
|
|
276
|
+
duration_ms = block.get("duration_ms", None),
|
|
277
|
+
end_ms = block.get("end_ms", None),
|
|
278
|
+
level_mA = block.get("level_mA", None),
|
|
279
|
+
max_mA = block.get("max_mA", None),
|
|
280
|
+
min_mA = block.get("min_mA", None),
|
|
281
|
+
range_mA = block.get("range_mA", None),
|
|
282
|
+
samples = block.get("samples", None),
|
|
283
|
+
start_ms = block.get("start_ms", None),
|
|
284
|
+
))
|
|
285
|
+
|
|
286
|
+
aem_clustering.configuration = types.AemClusterConfiguration(
|
|
287
|
+
false_alarm_probability = cluster_dict.get("configuration", {}).get("false_alarm_probability", None),
|
|
288
|
+
max_points = cluster_dict.get("configuration", {}).get("max_points", None),
|
|
289
|
+
min_segment_ms = cluster_dict.get("configuration", {}).get("min_segment_ms", None),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
aem_clustering.method = cluster_dict.get("method", None)
|
|
293
|
+
aem_clustering.total_blocks = cluster_dict.get("total_blocks", None)
|
|
294
|
+
aem_clustering.type = cluster_dict.get("type", None)
|
|
295
|
+
aem_clustering.unique_states = cluster_dict.get("unique_states", None)
|
|
296
|
+
|
|
297
|
+
period_dict : dict | None = result["result"].get("period_detection", None)
|
|
298
|
+
if period_dict:
|
|
299
|
+
aem_period_detection = types.AemPeriodDetection()
|
|
300
|
+
aem_period_detection.configuration = types.AemPeriodDetectionConfiguration(
|
|
301
|
+
max_period_ms = period_dict.get("configuration", {}).get("max_period_ms", None),
|
|
302
|
+
min_period_ms = period_dict.get("configuration", {}).get("min_period_ms", None),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
aem_period_detection.result = types.AemPeriodDetectionResult()
|
|
306
|
+
aem_period_detection.result.confidence = period_dict.get("result", {}).get("confidence", None)
|
|
307
|
+
aem_period_detection.result.frequency_hz = period_dict.get("result", {}).get("frequency_hz", None)
|
|
308
|
+
|
|
309
|
+
aem_period_detection.result.interval_summary = types.AemPeriodDetectionIntervalSummary(
|
|
310
|
+
average_mean_current_ma = period_dict.get("result", {}).get("interval_summary", {}).get("average_mean_current_ma", None),
|
|
311
|
+
average_peak_current_ma = period_dict.get("result", {}).get("interval_summary", {}).get("average_peak_current_ma", None),
|
|
312
|
+
max_period_ms = period_dict.get("result", {}).get("interval_summary", {}).get("max_period_ms", None),
|
|
313
|
+
min_period_ms = period_dict.get("result", {}).get("interval_summary", {}).get("min_period_ms", None),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
aem_period_detection.result.intervals = []
|
|
317
|
+
for interval in period_dict.get("result", {}).get("intervals", {}):
|
|
318
|
+
aem_period_detection.result.intervals.append(types.AemPeriodDetectionInterval(
|
|
319
|
+
cycle = interval.get("cycle", None),
|
|
320
|
+
end_index = interval.get("end_index", None),
|
|
321
|
+
end_ms = interval.get("end_ms", None),
|
|
322
|
+
mean_current_ma = interval.get("mean_current_ma", None),
|
|
323
|
+
peak_current_ma = interval.get("peak_current_ma", None),
|
|
324
|
+
period_ms = interval.get("period_ms", None),
|
|
325
|
+
start_index = interval.get("start_index", None),
|
|
326
|
+
start_ms = interval.get("start_ms", None),
|
|
327
|
+
))
|
|
328
|
+
|
|
329
|
+
aem_period_detection.result.is_periodic = period_dict.get("result", {}).get("is_periodic", None)
|
|
330
|
+
aem_period_detection.result.jitter_relative = period_dict.get("result", {}).get("jitter_relative", None)
|
|
331
|
+
aem_period_detection.result.method = period_dict.get("result", {}).get("method", None)
|
|
332
|
+
aem_period_detection.result.num_cycles = period_dict.get("result", {}).get("num_cycles", None)
|
|
333
|
+
aem_period_detection.result.period_ms = period_dict.get("result", {}).get("period_ms", None)
|
|
334
|
+
aem_period_detection.result.method_results = []
|
|
335
|
+
for method_result in period_dict.get("result", {}).get("method_results", {}):
|
|
336
|
+
aem_period_detection.result.method_results.append(types.AemPeriodDetectionMethodResult(
|
|
337
|
+
method = method_result.get("method", None),
|
|
338
|
+
detected = method_result.get("detected", None),
|
|
339
|
+
confidence = method_result.get("confidence", None),
|
|
340
|
+
period_ms = method_result.get("period_ms", None),
|
|
341
|
+
relative_error = method_result.get("relative_error", None),
|
|
342
|
+
))
|
|
343
|
+
|
|
344
|
+
aem_period_detection.type = period_dict.get("type", None)
|
|
345
|
+
|
|
346
|
+
signal_char_dict : dict | None = result["result"].get("signal_characteristics", None)
|
|
347
|
+
if signal_char_dict:
|
|
348
|
+
aem_signal_characteristics = types.AemSignalCharacteristics(
|
|
349
|
+
average_voltage_v = signal_char_dict.get("average_voltage_V", None),
|
|
350
|
+
dynamic_range_ratio = signal_char_dict.get("dynamic_range_ratio", None),
|
|
351
|
+
estimated_states = signal_char_dict.get("estimated_states", None),
|
|
352
|
+
max_current_ma = signal_char_dict.get("max_current_mA", None),
|
|
353
|
+
min_current_ma = signal_char_dict.get("min_current_mA", None),
|
|
354
|
+
noise_level_mad_sigma_ma = signal_char_dict.get("noise_level_mad_sigma_mA", None),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
return types.AemAnalysisResult(
|
|
358
|
+
distribution = aem_distribution,
|
|
359
|
+
clustering = aem_clustering,
|
|
360
|
+
period_detection = aem_period_detection,
|
|
361
|
+
signal_characteristics = aem_signal_characteristics,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def __get_current_firmware_version(self) -> str | None:
|
|
365
|
+
result : dict = self._commander.adapter.probe()
|
|
366
|
+
if not result["success"]:
|
|
367
|
+
return None
|
|
368
|
+
return result["result"].get("firmware_info", {}).get("fw_version", None)
|