PyOpenocdClient 0.1.1__tar.gz → 0.2.0__tar.gz
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.
- {pyopenocdclient-0.1.1/src/PyOpenocdClient.egg-info → pyopenocdclient-0.2.0}/PKG-INFO +22 -8
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/README.md +19 -5
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/pyproject.toml +14 -3
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0/src/PyOpenocdClient.egg-info}/PKG-INFO +22 -8
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/PyOpenocdClient.egg-info/SOURCES.txt +3 -0
- pyopenocdclient-0.2.0/src/PyOpenocdClient.egg-info/entry_points.txt +2 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/py_openocd_client/__init__.py +1 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/py_openocd_client/baseclient.py +57 -7
- pyopenocdclient-0.2.0/src/py_openocd_client/cli.py +99 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/py_openocd_client/client.py +72 -21
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/py_openocd_client/errors.py +38 -4
- pyopenocdclient-0.2.0/src/py_openocd_client/py.typed +0 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/LICENSE +0 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/setup.cfg +0 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/PyOpenocdClient.egg-info/dependency_links.txt +0 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/PyOpenocdClient.egg-info/top_level.txt +0 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/py_openocd_client/bp_parser.py +0 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/py_openocd_client/types.py +0 -0
- {pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/py_openocd_client/wp_parser.py +0 -0
|
@@ -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/
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
**PyOpenocdClient** is a Python library for controlling [OpenOCD](https://openocd.org)
|
|
9
9
|
software tool.
|
|
10
10
|
|
|
11
|
-
It allows to send
|
|
11
|
+
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, ...
|
|
12
|
+
|
|
13
|
+
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.
|
|
12
14
|
|
|
13
15
|
Main features of PyOpenocdClient:
|
|
14
16
|
|
|
15
|
-
*
|
|
17
|
+
* you can send any Tcl command to OpenOCD and obtain its result;
|
|
16
18
|
|
|
17
19
|
* shorcuts for quick use of most common OpenOCD commands are provided;
|
|
18
20
|
|
|
@@ -20,11 +22,11 @@ Main features of PyOpenocdClient:
|
|
|
20
22
|
|
|
21
23
|
* the code is fully covered via unit tests;
|
|
22
24
|
|
|
23
|
-
*
|
|
25
|
+
* integration testing regularly runs against multiple versions of OpenOCD;
|
|
24
26
|
|
|
25
27
|
* the code is multiplatform and portable — it does not have any dependencies except for the Python's standard library;
|
|
26
28
|
|
|
27
|
-
* fully open-source under a permissive license (MIT license).
|
|
29
|
+
* the library is fully open-source under a permissive license (MIT license).
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
## Quick instructions
|
|
@@ -35,7 +37,7 @@ Install PyOpenocdClient package using Pip:
|
|
|
35
37
|
$ python3 -m pip install PyOpenocdClient
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
Basic usage:
|
|
40
|
+
Basic usage from Python:
|
|
39
41
|
|
|
40
42
|
```python
|
|
41
43
|
from py_openocd_client import PyOpenocdClient
|
|
@@ -48,6 +50,18 @@ with PyOpenocdClient(host="localhost", port=6666) as ocd:
|
|
|
48
50
|
# ...
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
Basic usage from a command-line or shell scripts:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
|
|
57
|
+
$ openocd_cmd --host 127.0.0.1 --port 6666 version
|
|
58
|
+
Open On-Chip Debugger 0.12.0+dev-02634-g390b9d731 (2026-08-29-14:15)
|
|
59
|
+
|
|
60
|
+
$ openocd_cmd --host 127.0.0.1 --port 6666 "reset halt ; reg pc"
|
|
61
|
+
pc (/32): 0x08001234
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
51
65
|
## Documentation
|
|
52
66
|
|
|
53
67
|
For full documentation, please visit: https://pyopenocdclient.readthedocs.io/en/latest/
|
|
@@ -1,19 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["setuptools>=61"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
1
6
|
[project]
|
|
2
7
|
name = "PyOpenocdClient"
|
|
3
|
-
version = "0.
|
|
8
|
+
version = "0.2.0"
|
|
4
9
|
authors = [
|
|
5
10
|
{ name="Jan Matyas", email="info@janmatyas.net" },
|
|
6
11
|
]
|
|
7
12
|
description = "Library for controlling OpenOCD from Python programs"
|
|
8
13
|
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.
|
|
14
|
+
requires-python = ">=3.10"
|
|
10
15
|
classifiers = [
|
|
11
16
|
"Programming Language :: Python :: 3",
|
|
12
|
-
"License :: OSI Approved :: MIT License",
|
|
13
17
|
"Operating System :: OS Independent",
|
|
14
18
|
]
|
|
19
|
+
license = "MIT"
|
|
15
20
|
|
|
16
21
|
[project.urls]
|
|
17
22
|
Homepage = "https://github.com/HonzaMat/PyOpenocdClient"
|
|
18
23
|
Issues = "https://github.com/HonzaMat/PyOpenocdClient/issues"
|
|
19
24
|
Documentation = "https://pyopenocdclient.readthedocs.io/en/latest/"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
openocd_cmd = "py_openocd_client.cli:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-data]
|
|
30
|
+
py_openocd_client = ["py.typed"]
|
|
@@ -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/
|
|
@@ -4,11 +4,14 @@ pyproject.toml
|
|
|
4
4
|
src/PyOpenocdClient.egg-info/PKG-INFO
|
|
5
5
|
src/PyOpenocdClient.egg-info/SOURCES.txt
|
|
6
6
|
src/PyOpenocdClient.egg-info/dependency_links.txt
|
|
7
|
+
src/PyOpenocdClient.egg-info/entry_points.txt
|
|
7
8
|
src/PyOpenocdClient.egg-info/top_level.txt
|
|
8
9
|
src/py_openocd_client/__init__.py
|
|
9
10
|
src/py_openocd_client/baseclient.py
|
|
10
11
|
src/py_openocd_client/bp_parser.py
|
|
12
|
+
src/py_openocd_client/cli.py
|
|
11
13
|
src/py_openocd_client/client.py
|
|
12
14
|
src/py_openocd_client/errors.py
|
|
15
|
+
src/py_openocd_client/py.typed
|
|
13
16
|
src/py_openocd_client/types.py
|
|
14
17
|
src/py_openocd_client/wp_parser.py
|
|
@@ -94,10 +94,35 @@ class _PyOpenocdBaseClient:
|
|
|
94
94
|
raise ValueError("Timeout must be greater than zero")
|
|
95
95
|
self._default_recv_timeout = timeout
|
|
96
96
|
|
|
97
|
-
def
|
|
97
|
+
def _check_connection_before_command(self) -> None:
|
|
98
98
|
assert self._socket is not None
|
|
99
99
|
rd, _, _ = select.select([self._socket], [], [], 0) # Don't block, just poll
|
|
100
|
-
|
|
100
|
+
|
|
101
|
+
if len(rd) == 0:
|
|
102
|
+
# The socket is not ready for recv() now, which is the expected state.
|
|
103
|
+
# Success.
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# The socket is ready for recv(). This is unexpected at this point
|
|
107
|
+
# and always means an error. Try receive from the socket to find out
|
|
108
|
+
# what happened:
|
|
109
|
+
try:
|
|
110
|
+
recvd_data = self._socket.recv(128)
|
|
111
|
+
except OSError as e:
|
|
112
|
+
# This is unlikely to happen: It would mean that the socket is ready
|
|
113
|
+
# for recv() but then recv() failed.
|
|
114
|
+
raise OcdConnectionError(
|
|
115
|
+
"Connection to OpenOCD broken for an unknown reason"
|
|
116
|
+
) from e
|
|
117
|
+
|
|
118
|
+
if len(recvd_data) == 0:
|
|
119
|
+
# Empty received data means that the connection got closed by OpenOCD
|
|
120
|
+
# in the meanwhile.
|
|
121
|
+
raise OcdConnectionError("Connection closed by OpenOCD")
|
|
122
|
+
else:
|
|
123
|
+
# It looks like OpenOCD sent us some extra, unsolicited bytes (without us
|
|
124
|
+
# sending any command to OpenOCD). This is a violation of the communication
|
|
125
|
+
# protocol.
|
|
101
126
|
raise OcdConnectionError(
|
|
102
127
|
"Received unexpected bytes from OpenOCD before "
|
|
103
128
|
"the command was even sent."
|
|
@@ -107,12 +132,27 @@ class _PyOpenocdBaseClient:
|
|
|
107
132
|
assert self.is_connected()
|
|
108
133
|
assert self._socket is not None
|
|
109
134
|
|
|
110
|
-
#
|
|
111
|
-
|
|
135
|
+
# Perform basic connection check before sending a command.
|
|
136
|
+
# Note that this is merely a safety/correctness check which itself
|
|
137
|
+
# does not guarantee that the subsequent send() and recv() calls
|
|
138
|
+
# will succeed.
|
|
139
|
+
self._check_connection_before_command()
|
|
112
140
|
|
|
113
141
|
data = raw_cmd.encode(self.CHARSET) + self.COMMAND_DELIMITER
|
|
114
|
-
|
|
115
|
-
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
self._socket.settimeout(self.SEND_TIMEOUT)
|
|
145
|
+
except OSError as e:
|
|
146
|
+
raise OcdConnectionError(
|
|
147
|
+
"Could not send a command to OpenOCD, failed to set socket timeout"
|
|
148
|
+
) from e
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
self._socket.send(data)
|
|
152
|
+
except OSError as e:
|
|
153
|
+
raise OcdConnectionError(
|
|
154
|
+
"Could not send a command to OpenOCD, socket error occurred"
|
|
155
|
+
) from e
|
|
116
156
|
|
|
117
157
|
def _do_recv_response(self, raw_cmd: str, timeout: Optional[float] = None) -> str:
|
|
118
158
|
assert self.is_connected()
|
|
@@ -123,7 +163,13 @@ class _PyOpenocdBaseClient:
|
|
|
123
163
|
timeout if timeout is not None else self._default_recv_timeout
|
|
124
164
|
)
|
|
125
165
|
|
|
126
|
-
|
|
166
|
+
try:
|
|
167
|
+
self._socket.settimeout(self.RECV_POLL_TIMEOUT)
|
|
168
|
+
except OSError as e:
|
|
169
|
+
raise OcdConnectionError(
|
|
170
|
+
"Could not receive a response from OpenOCD, "
|
|
171
|
+
"failed to set socket timeout"
|
|
172
|
+
) from e
|
|
127
173
|
|
|
128
174
|
time_start = time.time()
|
|
129
175
|
while time.time() < (time_start + effective_timeout):
|
|
@@ -131,6 +177,10 @@ class _PyOpenocdBaseClient:
|
|
|
131
177
|
d = self._socket.recv(self.RECV_BLOCK_SIZE)
|
|
132
178
|
except socket.timeout:
|
|
133
179
|
continue
|
|
180
|
+
except OSError as e:
|
|
181
|
+
raise OcdConnectionError(
|
|
182
|
+
"Could not receive a response from OpenOCD, socket error occurred"
|
|
183
|
+
) from e
|
|
134
184
|
|
|
135
185
|
if d == b"":
|
|
136
186
|
raise OcdConnectionError("Connection closed by OpenOCD")
|
|
@@ -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())
|
|
@@ -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
|
|
|
@@ -211,25 +216,42 @@ class PyOpenocdClient:
|
|
|
211
216
|
raw_cmd = cmd
|
|
212
217
|
|
|
213
218
|
raw_cmd = "set CMD_RETCODE [ catch { " + raw_cmd + " } CMD_OUTPUT ] ; "
|
|
214
|
-
|
|
219
|
+
|
|
220
|
+
# Older OpenOCD versions - prior to 93f16eed4(*) - incorrectly trimmed trailing
|
|
221
|
+
# whitespace from the string passed to the return command. Work around this bug
|
|
222
|
+
# by wrapping the string by non-whitespace characters.
|
|
223
|
+
# (*): https://review.openocd.org/c/openocd/+/9084
|
|
224
|
+
raw_cmd += 'return "<$CMD_RETCODE,$CMD_OUTPUT>" ; '
|
|
215
225
|
|
|
216
226
|
raw_result = self.raw_cmd(raw_cmd, timeout=timeout)
|
|
217
227
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
def is_expected_raw_result(s: str) -> bool:
|
|
229
|
+
return (
|
|
230
|
+
s.startswith("<")
|
|
231
|
+
and s.endswith(">")
|
|
232
|
+
and re.match(r"^<-?\d+,", s) is not None
|
|
233
|
+
)
|
|
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
|
+
|
|
243
|
+
if not is_expected_raw_result(raw_result):
|
|
225
244
|
msg = (
|
|
226
245
|
"Received unexpected response from OpenOCD. "
|
|
227
246
|
"It looks like OpenOCD misbehaves. "
|
|
228
247
|
)
|
|
229
248
|
raise OcdInvalidResponseError(msg, raw_cmd, raw_result)
|
|
230
249
|
|
|
231
|
-
|
|
232
|
-
|
|
250
|
+
# Remove leading "<" and trailing ">"
|
|
251
|
+
raw_result = raw_result[1:-1]
|
|
252
|
+
raw_result_parts = raw_result.split(",", maxsplit=1)
|
|
253
|
+
assert len(raw_result_parts) == 2
|
|
254
|
+
|
|
233
255
|
retcode = int(raw_result_parts[0], 10)
|
|
234
256
|
out = raw_result_parts[1] if len(raw_result_parts) == 2 else ""
|
|
235
257
|
|
|
@@ -663,11 +685,37 @@ class PyOpenocdClient:
|
|
|
663
685
|
"""
|
|
664
686
|
self.disconnect()
|
|
665
687
|
|
|
666
|
-
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:
|
|
667
692
|
"""
|
|
668
|
-
Shut down the OpenOCD process by sending the ``shutdown``
|
|
669
|
-
|
|
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``.
|
|
670
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
|
+
|
|
671
719
|
# Different OpenOCD versions respond to "shutdown" command differently:
|
|
672
720
|
#
|
|
673
721
|
# - OpenOCD 0.12.0 and older:
|
|
@@ -675,16 +723,19 @@ class PyOpenocdClient:
|
|
|
675
723
|
# which can be obtained normally as for any other TCL command -
|
|
676
724
|
# e.g. via the "catch" command.
|
|
677
725
|
#
|
|
678
|
-
# - OpenOCD 0.13.0-dev and newer (from
|
|
726
|
+
# - OpenOCD 0.13.0-dev and newer (starting from commit "93f16eed4"):
|
|
679
727
|
# The "shutdown" command immediately ends the TCL processing and
|
|
680
728
|
# an empty response is sent back to the TCL client.
|
|
681
729
|
|
|
682
|
-
#
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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()
|
|
688
739
|
|
|
689
740
|
def raw_cmd(self, raw_cmd: str, timeout: Optional[float] = None) -> str:
|
|
690
741
|
"""
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
{pyopenocdclient-0.1.1 → pyopenocdclient-0.2.0}/src/PyOpenocdClient.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|