PyOpenocdClient 0.1.2__py3-none-any.whl → 0.2.0__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.
- py_openocd_client/__init__.py +1 -0
- py_openocd_client/cli.py +99 -0
- py_openocd_client/client.py +53 -11
- py_openocd_client/errors.py +38 -4
- py_openocd_client/py.typed +0 -0
- {pyopenocdclient-0.1.2.dist-info → pyopenocdclient-0.2.0.dist-info}/METADATA +22 -8
- pyopenocdclient-0.2.0.dist-info/RECORD +15 -0
- {pyopenocdclient-0.1.2.dist-info → pyopenocdclient-0.2.0.dist-info}/WHEEL +1 -1
- pyopenocdclient-0.2.0.dist-info/entry_points.txt +2 -0
- pyopenocdclient-0.1.2.dist-info/RECORD +0 -12
- {pyopenocdclient-0.1.2.dist-info → pyopenocdclient-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {pyopenocdclient-0.1.2.dist-info → pyopenocdclient-0.2.0.dist-info}/top_level.txt +0 -0
py_openocd_client/__init__.py
CHANGED
py_openocd_client/cli.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from .client import PyOpenocdClient
|
|
7
|
+
from .errors import OcdCommandTimeoutError, OcdConnectionError, OcdInvalidResponseError
|
|
8
|
+
|
|
9
|
+
_DEFAULT_HOST = "127.0.0.1"
|
|
10
|
+
_DEFAULT_PORT = 6666
|
|
11
|
+
_DEFAULT_TIMEOUT = 5.0
|
|
12
|
+
|
|
13
|
+
_EXIT_CODE_CONNECTION_ERROR = 91
|
|
14
|
+
_EXIT_CODE_INVALID_RESPONSE = 92
|
|
15
|
+
_EXIT_CODE_COMMAND_TIMEOUT = 93
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def eprint(s: str) -> None:
|
|
19
|
+
print("error: " + s, file=sys.stderr)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_args() -> argparse.Namespace:
|
|
23
|
+
desc = (
|
|
24
|
+
"Command-line utility that sends a single Tcl command to OpenOCD\n"
|
|
25
|
+
"through its Tcl-RPC interface."
|
|
26
|
+
)
|
|
27
|
+
epilog = (
|
|
28
|
+
"If the Tcl command completes successfully, this tool exits with code 0.\n"
|
|
29
|
+
"Connection errors or command execution errors cause it to exit with\n"
|
|
30
|
+
"a non-zero code.\n\n"
|
|
31
|
+
"All the output of the Tcl command is written to stdout.\n"
|
|
32
|
+
"Any error messages coming from this tool are written to stderr."
|
|
33
|
+
)
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
description=desc,
|
|
36
|
+
epilog=epilog,
|
|
37
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
38
|
+
)
|
|
39
|
+
parser.color = False
|
|
40
|
+
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--host",
|
|
43
|
+
default=_DEFAULT_HOST,
|
|
44
|
+
help=(
|
|
45
|
+
"Hostname or address of the machine where OpenOCD is running "
|
|
46
|
+
f"(default: {_DEFAULT_HOST})"
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--port",
|
|
51
|
+
type=int,
|
|
52
|
+
default=_DEFAULT_PORT,
|
|
53
|
+
help=f"OpenOCD's Tcl-RPC server port number (default: {_DEFAULT_PORT})",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--timeout",
|
|
57
|
+
type=float,
|
|
58
|
+
default=_DEFAULT_TIMEOUT,
|
|
59
|
+
help=(
|
|
60
|
+
"Timeout for the command execution in seconds "
|
|
61
|
+
f"(default: {_DEFAULT_TIMEOUT:.1f})"
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"command",
|
|
66
|
+
help="The Tcl command to send to OpenOCD",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return parser.parse_args()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main() -> int:
|
|
73
|
+
args = parse_args()
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
with PyOpenocdClient(args.host, args.port) as ocd:
|
|
77
|
+
ocd.set_default_timeout(args.timeout)
|
|
78
|
+
result = ocd.cmd(args.command, throw=False)
|
|
79
|
+
print(result.out)
|
|
80
|
+
|
|
81
|
+
if result.retcode != 0:
|
|
82
|
+
eprint(f"The command has failed (return code: {result.retcode}).")
|
|
83
|
+
return result.retcode
|
|
84
|
+
|
|
85
|
+
except OcdConnectionError as e:
|
|
86
|
+
eprint(str(e))
|
|
87
|
+
return _EXIT_CODE_CONNECTION_ERROR
|
|
88
|
+
|
|
89
|
+
except OcdInvalidResponseError as e:
|
|
90
|
+
eprint("OpenOCD responded unexpectedly: " + str(e))
|
|
91
|
+
return _EXIT_CODE_INVALID_RESPONSE
|
|
92
|
+
|
|
93
|
+
except OcdCommandTimeoutError as e:
|
|
94
|
+
eprint(f"Timeout: The command did not complete within {e.timeout:.1f} seconds.")
|
|
95
|
+
return _EXIT_CODE_COMMAND_TIMEOUT
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__": # pragma: no cover
|
|
99
|
+
sys.exit(main())
|
py_openocd_client/client.py
CHANGED
|
@@ -7,7 +7,12 @@ from typing import Any, List, Optional, Tuple, Type
|
|
|
7
7
|
|
|
8
8
|
from .baseclient import _PyOpenocdBaseClient
|
|
9
9
|
from .bp_parser import _BpParser
|
|
10
|
-
from .errors import
|
|
10
|
+
from .errors import (
|
|
11
|
+
OcdCommandFailedError,
|
|
12
|
+
OcdEmptyResponseError,
|
|
13
|
+
OcdInvalidResponseError,
|
|
14
|
+
_OcdParsingError,
|
|
15
|
+
)
|
|
11
16
|
from .types import BpInfo, OcdCommandResult, WpInfo, WpType
|
|
12
17
|
from .wp_parser import _WpParser
|
|
13
18
|
|
|
@@ -227,6 +232,14 @@ class PyOpenocdClient:
|
|
|
227
232
|
and re.match(r"^<-?\d+,", s) is not None
|
|
228
233
|
)
|
|
229
234
|
|
|
235
|
+
if len(raw_result) == 0:
|
|
236
|
+
msg = (
|
|
237
|
+
"Received empty response from OpenOCD. "
|
|
238
|
+
"(This is OK for 'shutdown' or 'exit' commands but unexpected "
|
|
239
|
+
"for any other command.) "
|
|
240
|
+
)
|
|
241
|
+
raise OcdEmptyResponseError(msg, raw_cmd, raw_result)
|
|
242
|
+
|
|
230
243
|
if not is_expected_raw_result(raw_result):
|
|
231
244
|
msg = (
|
|
232
245
|
"Received unexpected response from OpenOCD. "
|
|
@@ -672,11 +685,37 @@ class PyOpenocdClient:
|
|
|
672
685
|
"""
|
|
673
686
|
self.disconnect()
|
|
674
687
|
|
|
675
|
-
def
|
|
688
|
+
def _shutdown_supports_any_exit_code(self) -> bool:
|
|
689
|
+
return "shutdown ['error'|exit_code]" in self.cmd("help shutdown").out
|
|
690
|
+
|
|
691
|
+
def shutdown(self, exit_code: int = 0) -> None:
|
|
676
692
|
"""
|
|
677
|
-
Shut down the OpenOCD process by sending the ``shutdown``
|
|
678
|
-
|
|
693
|
+
Shut down (terminate) the OpenOCD process by sending the ``shutdown``
|
|
694
|
+
command to it. PyOpenocdClient also immediately disconnects from OpenOCD.
|
|
695
|
+
|
|
696
|
+
The optional argument ``exit_code`` allows to set the exit code
|
|
697
|
+
of the OpenOCD process. Default is 0 (success).
|
|
698
|
+
|
|
699
|
+
.. versionadded:: 0.2.0
|
|
700
|
+
Argument ``exit_code``.
|
|
679
701
|
"""
|
|
702
|
+
if not (0 <= exit_code <= 255):
|
|
703
|
+
raise ValueError("The exit_status must be in range 0..255.")
|
|
704
|
+
|
|
705
|
+
# For compatibility with older OpenOCD, prefer:
|
|
706
|
+
# - "shutdown" over "shutdown 0"
|
|
707
|
+
# - "shutdown error" over "shutdown 1"
|
|
708
|
+
if exit_code == 0:
|
|
709
|
+
cmd = "shutdown"
|
|
710
|
+
elif exit_code == 1:
|
|
711
|
+
cmd = "shutdown error"
|
|
712
|
+
else:
|
|
713
|
+
if not self._shutdown_supports_any_exit_code():
|
|
714
|
+
raise ValueError(
|
|
715
|
+
"This version of OpenOCD supports exit code 0 or 1 only"
|
|
716
|
+
)
|
|
717
|
+
cmd = f"shutdown {exit_code}"
|
|
718
|
+
|
|
680
719
|
# Different OpenOCD versions respond to "shutdown" command differently:
|
|
681
720
|
#
|
|
682
721
|
# - OpenOCD 0.12.0 and older:
|
|
@@ -684,16 +723,19 @@ class PyOpenocdClient:
|
|
|
684
723
|
# which can be obtained normally as for any other TCL command -
|
|
685
724
|
# e.g. via the "catch" command.
|
|
686
725
|
#
|
|
687
|
-
# - OpenOCD 0.13.0-dev and newer (from
|
|
726
|
+
# - OpenOCD 0.13.0-dev and newer (starting from commit "93f16eed4"):
|
|
688
727
|
# The "shutdown" command immediately ends the TCL processing and
|
|
689
728
|
# an empty response is sent back to the TCL client.
|
|
690
729
|
|
|
691
|
-
#
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
730
|
+
# Tolerate both the above scenarios:
|
|
731
|
+
try:
|
|
732
|
+
self.cmd(cmd)
|
|
733
|
+
except OcdCommandFailedError:
|
|
734
|
+
pass
|
|
735
|
+
except OcdEmptyResponseError:
|
|
736
|
+
pass
|
|
737
|
+
finally:
|
|
738
|
+
self.disconnect()
|
|
697
739
|
|
|
698
740
|
def raw_cmd(self, raw_cmd: str, timeout: Optional[float] = None) -> str:
|
|
699
741
|
"""
|
py_openocd_client/errors.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
3
5
|
from .types import OcdCommandResult
|
|
4
6
|
|
|
5
7
|
|
|
@@ -87,9 +89,9 @@ class OcdInvalidResponseError(OcdBaseException):
|
|
|
87
89
|
That is, PyOpenocdClient could not parse and/or interpret that command output.
|
|
88
90
|
"""
|
|
89
91
|
|
|
90
|
-
def __init__(self, msg: str, raw_cmd: str,
|
|
92
|
+
def __init__(self, msg: str, raw_cmd: str, raw_out: str):
|
|
91
93
|
self._raw_cmd = raw_cmd
|
|
92
|
-
self.
|
|
94
|
+
self._raw_out = raw_out
|
|
93
95
|
super().__init__(msg)
|
|
94
96
|
|
|
95
97
|
@property
|
|
@@ -99,12 +101,44 @@ class OcdInvalidResponseError(OcdBaseException):
|
|
|
99
101
|
"""
|
|
100
102
|
return self._raw_cmd
|
|
101
103
|
|
|
104
|
+
@property
|
|
105
|
+
def raw_out(self) -> str:
|
|
106
|
+
"""
|
|
107
|
+
The actual raw command's output that was unexpected
|
|
108
|
+
(that could not be understood or parsed).
|
|
109
|
+
|
|
110
|
+
.. versionadded:: 0.2.0
|
|
111
|
+
"""
|
|
112
|
+
return self._raw_out
|
|
113
|
+
|
|
102
114
|
@property
|
|
103
115
|
def out(self) -> str:
|
|
104
116
|
"""
|
|
105
|
-
|
|
117
|
+
Deprecated alias for ``raw_out``, kept just for compatibility.
|
|
118
|
+
|
|
119
|
+
.. deprecated:: 0.2.0
|
|
106
120
|
"""
|
|
107
|
-
|
|
121
|
+
warnings.warn(
|
|
122
|
+
"OcdInvalidResponseError.out is deprecated. "
|
|
123
|
+
"Please use OcdInvalidResponseError.raw_out instead.",
|
|
124
|
+
DeprecationWarning,
|
|
125
|
+
)
|
|
126
|
+
return self._raw_out
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class OcdEmptyResponseError(OcdInvalidResponseError):
|
|
130
|
+
"""
|
|
131
|
+
Exception which denotes that a TCL command produced an empty response.
|
|
132
|
+
It is a sub-class of :py:class:`py_openocd_client.OcdInvalidResponseError`.
|
|
133
|
+
|
|
134
|
+
Empty responses occur for commands ``exit`` and ``shutdown``, that is, commands
|
|
135
|
+
that immediately terminate the TCL session. For other commands, empty responses are
|
|
136
|
+
unexpected and would mean that OpenOCD mis-behaves.
|
|
137
|
+
|
|
138
|
+
.. versionadded:: 0.2.0
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
pass
|
|
108
142
|
|
|
109
143
|
|
|
110
144
|
class OcdConnectionError(OcdBaseException):
|
|
File without changes
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyOpenocdClient
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Library for controlling OpenOCD from Python programs
|
|
5
5
|
Author-email: Jan Matyas <info@janmatyas.net>
|
|
6
|
+
License-Expression: MIT
|
|
6
7
|
Project-URL: Homepage, https://github.com/HonzaMat/PyOpenocdClient
|
|
7
8
|
Project-URL: Issues, https://github.com/HonzaMat/PyOpenocdClient/issues
|
|
8
9
|
Project-URL: Documentation, https://pyopenocdclient.readthedocs.io/en/latest/
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Dynamic: license-file
|
|
@@ -24,11 +24,13 @@ Dynamic: license-file
|
|
|
24
24
|
**PyOpenocdClient** is a Python library for controlling [OpenOCD](https://openocd.org)
|
|
25
25
|
software tool.
|
|
26
26
|
|
|
27
|
-
It allows to send
|
|
27
|
+
It allows to send Tcl commands from Python programs to OpenOCD — for instance commands like halt execution of the program, view data in memory, place breakpoints, single-step, ...
|
|
28
|
+
|
|
29
|
+
In addition, a console utility is included with PyOpenocdClient that can be used to send Tcl commands to OpenOCD from non-Python software, such as shell scripts.
|
|
28
30
|
|
|
29
31
|
Main features of PyOpenocdClient:
|
|
30
32
|
|
|
31
|
-
*
|
|
33
|
+
* you can send any Tcl command to OpenOCD and obtain its result;
|
|
32
34
|
|
|
33
35
|
* shorcuts for quick use of most common OpenOCD commands are provided;
|
|
34
36
|
|
|
@@ -36,11 +38,11 @@ Main features of PyOpenocdClient:
|
|
|
36
38
|
|
|
37
39
|
* the code is fully covered via unit tests;
|
|
38
40
|
|
|
39
|
-
*
|
|
41
|
+
* integration testing regularly runs against multiple versions of OpenOCD;
|
|
40
42
|
|
|
41
43
|
* the code is multiplatform and portable — it does not have any dependencies except for the Python's standard library;
|
|
42
44
|
|
|
43
|
-
* fully open-source under a permissive license (MIT license).
|
|
45
|
+
* the library is fully open-source under a permissive license (MIT license).
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
## Quick instructions
|
|
@@ -51,7 +53,7 @@ Install PyOpenocdClient package using Pip:
|
|
|
51
53
|
$ python3 -m pip install PyOpenocdClient
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
Basic usage:
|
|
56
|
+
Basic usage from Python:
|
|
55
57
|
|
|
56
58
|
```python
|
|
57
59
|
from py_openocd_client import PyOpenocdClient
|
|
@@ -64,6 +66,18 @@ with PyOpenocdClient(host="localhost", port=6666) as ocd:
|
|
|
64
66
|
# ...
|
|
65
67
|
```
|
|
66
68
|
|
|
69
|
+
Basic usage from a command-line or shell scripts:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
|
|
73
|
+
$ openocd_cmd --host 127.0.0.1 --port 6666 version
|
|
74
|
+
Open On-Chip Debugger 0.12.0+dev-02634-g390b9d731 (2026-08-29-14:15)
|
|
75
|
+
|
|
76
|
+
$ openocd_cmd --host 127.0.0.1 --port 6666 "reset halt ; reg pc"
|
|
77
|
+
pc (/32): 0x08001234
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
|
|
67
81
|
## Documentation
|
|
68
82
|
|
|
69
83
|
For full documentation, please visit: https://pyopenocdclient.readthedocs.io/en/latest/
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
py_openocd_client/__init__.py,sha256=1vwYXd_WD5F35hhS4lF8BkE_aPo6xpnIGaHlEjkUPkM,359
|
|
2
|
+
py_openocd_client/baseclient.py,sha256=BkwX79VqrZkZ_vGk4QnnD9PXGKn8erueMuwDpcAD7GA,9103
|
|
3
|
+
py_openocd_client/bp_parser.py,sha256=yQw8qrK6GmZhR5sIdzjD90zW0StQVUkgAerwqiB08pg,4013
|
|
4
|
+
py_openocd_client/cli.py,sha256=MVtIAIj-60ZPfDKJmOgQTg8YAYTOUtdLkS6CQQTKM74,2815
|
|
5
|
+
py_openocd_client/client.py,sha256=RlZCx6Cf3HdLpfIjBFvz8oNyz6GXYKc4g2LObedXhjw,27760
|
|
6
|
+
py_openocd_client/errors.py,sha256=7uEneGsOgwkwGDKteTPv79Y1REdb0ZU-sALzJK8wZe0,4712
|
|
7
|
+
py_openocd_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
py_openocd_client/types.py,sha256=gOoND2jpGqfAqKDE-LwowTL6eHlTd9GHNsAJQvl8C5o,2204
|
|
9
|
+
py_openocd_client/wp_parser.py,sha256=gcwewLfMus26vve7yPDZnW7kQKvnZpWFJ-CnxPw1Acc,1583
|
|
10
|
+
pyopenocdclient-0.2.0.dist-info/licenses/LICENSE,sha256=LQ4E7j3KmZ2D4Bx0cNd7_k39v0KJlF72VjOAl6k5Aas,1094
|
|
11
|
+
pyopenocdclient-0.2.0.dist-info/METADATA,sha256=2ZbRgwjIgQyhlEUsCI3Tm2U-pTi4GP25eRkf4rRoXB8,3245
|
|
12
|
+
pyopenocdclient-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
13
|
+
pyopenocdclient-0.2.0.dist-info/entry_points.txt,sha256=iViuS3xCpmJHGh4vtSWX7IlczcwCSC9gzxH_6BKqAwM,59
|
|
14
|
+
pyopenocdclient-0.2.0.dist-info/top_level.txt,sha256=YzDCf323Zc2HbUuT7wyJ2IHg8vlELGt9Ve0k_u3lhMk,18
|
|
15
|
+
pyopenocdclient-0.2.0.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
py_openocd_client/__init__.py,sha256=THbr8De7S91EatjLlLIDbkWNLoGAQf5j6kn69xOqGHg,332
|
|
2
|
-
py_openocd_client/baseclient.py,sha256=BkwX79VqrZkZ_vGk4QnnD9PXGKn8erueMuwDpcAD7GA,9103
|
|
3
|
-
py_openocd_client/bp_parser.py,sha256=yQw8qrK6GmZhR5sIdzjD90zW0StQVUkgAerwqiB08pg,4013
|
|
4
|
-
py_openocd_client/client.py,sha256=BuqJfL_DF_vpvnS8m-BVugUx6GQbEy4qV7jKGk0ov64,26424
|
|
5
|
-
py_openocd_client/errors.py,sha256=9K48w4rVgaOkCF4q3ORp3KxH31DVQL0JNXoGktgE-H0,3703
|
|
6
|
-
py_openocd_client/types.py,sha256=gOoND2jpGqfAqKDE-LwowTL6eHlTd9GHNsAJQvl8C5o,2204
|
|
7
|
-
py_openocd_client/wp_parser.py,sha256=gcwewLfMus26vve7yPDZnW7kQKvnZpWFJ-CnxPw1Acc,1583
|
|
8
|
-
pyopenocdclient-0.1.2.dist-info/licenses/LICENSE,sha256=LQ4E7j3KmZ2D4Bx0cNd7_k39v0KJlF72VjOAl6k5Aas,1094
|
|
9
|
-
pyopenocdclient-0.1.2.dist-info/METADATA,sha256=00dFEgphjGU15wTOueEC9g7RXsqf3rmxqxbuLC1VmQg,2804
|
|
10
|
-
pyopenocdclient-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
-
pyopenocdclient-0.1.2.dist-info/top_level.txt,sha256=YzDCf323Zc2HbUuT7wyJ2IHg8vlELGt9Ve0k_u3lhMk,18
|
|
12
|
-
pyopenocdclient-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|