sertools 0.1.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.
- sertools-0.1.0/LICENSE +21 -0
- sertools-0.1.0/PKG-INFO +44 -0
- sertools-0.1.0/README.md +29 -0
- sertools-0.1.0/pyproject.toml +31 -0
- sertools-0.1.0/setup.cfg +4 -0
- sertools-0.1.0/src/sertools/__init__.py +3 -0
- sertools-0.1.0/src/sertools/scripts/__init__.py +0 -0
- sertools-0.1.0/src/sertools/scripts/sterm.py +125 -0
- sertools-0.1.0/src/sertools/serial_device.py +303 -0
- sertools-0.1.0/src/sertools.egg-info/PKG-INFO +44 -0
- sertools-0.1.0/src/sertools.egg-info/SOURCES.txt +13 -0
- sertools-0.1.0/src/sertools.egg-info/dependency_links.txt +1 -0
- sertools-0.1.0/src/sertools.egg-info/entry_points.txt +2 -0
- sertools-0.1.0/src/sertools.egg-info/requires.txt +2 -0
- sertools-0.1.0/src/sertools.egg-info/top_level.txt +1 -0
sertools-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason Doar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sertools-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sertools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interface for serial devices using pyserial.
|
|
5
|
+
Author-email: Jason Doar <jbdoar@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jbdoar/sertools
|
|
8
|
+
Project-URL: Issues, https://github.com/jbdoar/sertools/issues
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pyserial
|
|
13
|
+
Requires-Dist: readchar
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# sertools
|
|
17
|
+
|
|
18
|
+
Interface for serial devices using pyserial.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install sertools
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
- Query method with flexible configuration parameters for various response formats.
|
|
28
|
+
- Lightweight serial port terminal emulator.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
The SerialDevice instance may be called with a command string to send to the serial port and optional readback/terminator parameters.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from sertools import SerialDevice
|
|
35
|
+
ser = SerialDevice()
|
|
36
|
+
ser('HELP')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Development
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/jbdoar/sertools
|
|
42
|
+
cd sertools
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
sertools-0.1.0/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# sertools
|
|
2
|
+
|
|
3
|
+
Interface for serial devices using pyserial.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install sertools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
- Query method with flexible configuration parameters for various response formats.
|
|
13
|
+
- Lightweight serial port terminal emulator.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
The SerialDevice instance may be called with a command string to send to the serial port and optional readback/terminator parameters.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from sertools import SerialDevice
|
|
20
|
+
ser = SerialDevice()
|
|
21
|
+
ser('HELP')
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Development
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/jbdoar/sertools
|
|
27
|
+
cd sertools
|
|
28
|
+
pip install -e .
|
|
29
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# pyproject.toml
|
|
2
|
+
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["setuptools>=64", "wheel"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "sertools"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "Interface for serial devices using pyserial."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
authors = [{name = "Jason Doar", email = "jbdoar@gmail.com"}]
|
|
14
|
+
dependencies = ["pyserial", "readchar"]
|
|
15
|
+
license = "MIT"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/jbdoar/sertools"
|
|
20
|
+
Issues = "https://github.com/jbdoar/sertools/issues"
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
# serial terminal emulator
|
|
24
|
+
sterm = "sertools.scripts.sterm:main"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
package-dir = {"" = "src"}
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
sertools-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
import readchar
|
|
7
|
+
|
|
8
|
+
import sertools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger('sertools')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def configure_logging(logfile=None):
|
|
15
|
+
"""
|
|
16
|
+
"""
|
|
17
|
+
log.setLevel(logging.INFO)
|
|
18
|
+
log.handlers.clear()
|
|
19
|
+
log.propagate = False
|
|
20
|
+
|
|
21
|
+
if logfile:
|
|
22
|
+
file_handler = logging.FileHandler(logfile, encoding='utf-8')
|
|
23
|
+
file_handler.setLevel(logging.INFO)
|
|
24
|
+
file_handler.setFormatter(logging.Formatter('%(asctime)s %(message)s', datefmt='%Y-%m-%d %H:%M%S'))
|
|
25
|
+
log.addHandler(file_handler)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def reader(ser, stop_event):
|
|
29
|
+
"""
|
|
30
|
+
"""
|
|
31
|
+
buf = bytearray()
|
|
32
|
+
|
|
33
|
+
while not stop_event.is_set():
|
|
34
|
+
try:
|
|
35
|
+
n = ser.ser.in_waiting
|
|
36
|
+
data = ser.ser.read(n or 1)
|
|
37
|
+
except Exception:
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
if not data:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
text = data.decode(ser.encoding, errors='replace')
|
|
44
|
+
sys.stdout.write(text)
|
|
45
|
+
sys.stdout.flush()
|
|
46
|
+
|
|
47
|
+
buf.extend(data)
|
|
48
|
+
|
|
49
|
+
while True:
|
|
50
|
+
i_crlf = buf.find(b'\r\n')
|
|
51
|
+
i_cr = buf.find(b'\r')
|
|
52
|
+
i_lf = buf.find(b'\n')
|
|
53
|
+
|
|
54
|
+
matches = [(i, 2) for i in [i_crlf] if i != -1]
|
|
55
|
+
matches += [(i, 1) for i in [i_cr, i_lf] if i != -1]
|
|
56
|
+
|
|
57
|
+
if not matches:
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
i, nl_len = min(matches, key=lambda x: x[0])
|
|
61
|
+
line = bytes(buf[:i]).decode(ser.encoding, errors='replace')
|
|
62
|
+
del buf[:i + nl_len]
|
|
63
|
+
|
|
64
|
+
if line:
|
|
65
|
+
log.info('RX: %r', line)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def send_raw(ser, s):
|
|
69
|
+
"""
|
|
70
|
+
"""
|
|
71
|
+
sys.stdout.write(s)
|
|
72
|
+
sys.stdout.flush()
|
|
73
|
+
ser.write(s, append_newline=False)
|
|
74
|
+
log.info('TX: %r', s)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def terminal(ser):
|
|
79
|
+
stop_event = threading.Event()
|
|
80
|
+
threading.Thread(target=reader, args=(ser, stop_event), daemon=True).start()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
while True:
|
|
84
|
+
try:
|
|
85
|
+
key = readchar.readkey()
|
|
86
|
+
except KeyboardInterrupt:
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
if key == readchar.key.ESC:
|
|
90
|
+
send_raw(ser, '\x1b')
|
|
91
|
+
elif key == readchar.key.BACKSPACE:
|
|
92
|
+
send_raw(ser, '\x08')
|
|
93
|
+
elif key in (readchar.key.ENTER, readchar.key.CR, readchar.key.LF):
|
|
94
|
+
send_raw(ser, ser.newline_tx or '\r')
|
|
95
|
+
else:
|
|
96
|
+
send_raw(ser, key)
|
|
97
|
+
finally:
|
|
98
|
+
stop_event.set()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main():
|
|
102
|
+
parser = argparse.ArgumentParser()
|
|
103
|
+
parser.add_argument('port')
|
|
104
|
+
parser.add_argument('baudrate', type=int)
|
|
105
|
+
parser.add_argument('--log', help='path to log file')
|
|
106
|
+
args = parser.parse_args()
|
|
107
|
+
|
|
108
|
+
configure_logging(args.log)
|
|
109
|
+
|
|
110
|
+
ser = sertools.SerialDevice(
|
|
111
|
+
port=args.port,
|
|
112
|
+
baudrate=args.baudrate,
|
|
113
|
+
newline_rx='\r',
|
|
114
|
+
terminator=None,
|
|
115
|
+
terminator_cmd=None,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
terminal(ser)
|
|
120
|
+
finally:
|
|
121
|
+
ser.close()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
main()
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""sertools.py module, a thin pyserial wrapper"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import serial
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SerialDevice:
|
|
13
|
+
"""Thin wrapper for pyserial.Serial class.
|
|
14
|
+
Provides a flexible query method for sending commands and receiving responses.
|
|
15
|
+
Responses are always either a str (single line) or list of str (multiline).
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
port : str
|
|
20
|
+
baudrate : int
|
|
21
|
+
timeout : float
|
|
22
|
+
encoding : str
|
|
23
|
+
newline_tx : str
|
|
24
|
+
newline_rx : str
|
|
25
|
+
terminator : str
|
|
26
|
+
terminator_cmd : str
|
|
27
|
+
|
|
28
|
+
Examples
|
|
29
|
+
--------
|
|
30
|
+
>>> dut = SerialDevice(port='COM42',
|
|
31
|
+
baudrate=460800,
|
|
32
|
+
timeout=None,
|
|
33
|
+
newline_tx='\r',
|
|
34
|
+
newline_rx='\r\n',
|
|
35
|
+
terminator='Ok',
|
|
36
|
+
terminator_cmd='\r')
|
|
37
|
+
"""
|
|
38
|
+
def __init__(self, *,
|
|
39
|
+
port: str | None = None,
|
|
40
|
+
baudrate: int = 9600,
|
|
41
|
+
timeout: float | None = None,
|
|
42
|
+
encoding: str = 'ascii',
|
|
43
|
+
newline_tx: str = '\r',
|
|
44
|
+
newline_rx: str = '\r',
|
|
45
|
+
terminator: str | None = None,
|
|
46
|
+
terminator_cmd: str | None = None):
|
|
47
|
+
|
|
48
|
+
self.port = port
|
|
49
|
+
self.baudrate = baudrate
|
|
50
|
+
self.timeout = timeout
|
|
51
|
+
self.encoding = encoding
|
|
52
|
+
self.newline_tx = newline_tx
|
|
53
|
+
self.newline_rx = newline_rx
|
|
54
|
+
self.terminator = terminator
|
|
55
|
+
self.terminator_cmd = terminator_cmd
|
|
56
|
+
self._rx_buffer = bytearray()
|
|
57
|
+
|
|
58
|
+
self.ser = serial.Serial(port=port,
|
|
59
|
+
baudrate=baudrate,
|
|
60
|
+
timeout=timeout)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def __repr__(self):
|
|
64
|
+
return f"{self.__class__.__name__}(port={self.port!r}, baudrate={self.baudrate})"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def __str__(self):
|
|
68
|
+
return f"{self.port} @ {self.baudrate}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def __call__(self, command: str, **kwargs) -> str | list[str]:
|
|
72
|
+
"""Passes `command` and other params along to `self.query`."""
|
|
73
|
+
|
|
74
|
+
response = self.query(command, **kwargs)
|
|
75
|
+
return response
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def open(self) -> None:
|
|
79
|
+
"""Open port."""
|
|
80
|
+
self.ser.open()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def close(self) -> None:
|
|
84
|
+
"""Close port."""
|
|
85
|
+
self.ser.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def flush(self) -> None:
|
|
89
|
+
"""Reset input and output buffers."""
|
|
90
|
+
self.ser.reset_input_buffer()
|
|
91
|
+
self.ser.reset_output_buffer()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def write(self, command: str,
|
|
95
|
+
newline_tx: str | None = None,
|
|
96
|
+
append_newline: bool = True) -> int:
|
|
97
|
+
"""Write command to port.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
command : str
|
|
102
|
+
Command sent to device.
|
|
103
|
+
newline_tx : str, optional
|
|
104
|
+
Newline character denoting end of line sent.
|
|
105
|
+
Defaults to `self.newline_tx'
|
|
106
|
+
append_newline : bool, optional
|
|
107
|
+
Optionally append newline_tx to command if not already present.
|
|
108
|
+
Defaults to True.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
int
|
|
113
|
+
Number of bytes sent to port.
|
|
114
|
+
|
|
115
|
+
Examples
|
|
116
|
+
--------
|
|
117
|
+
>>> TBD
|
|
118
|
+
"""
|
|
119
|
+
newline_tx = self.newline_tx if newline_tx is None else newline_tx
|
|
120
|
+
|
|
121
|
+
if append_newline and not command.endswith(newline_tx):
|
|
122
|
+
command += newline_tx
|
|
123
|
+
|
|
124
|
+
log.info("TX: %r", command)
|
|
125
|
+
tx = command.encode(self.encoding)
|
|
126
|
+
return self.ser.write(tx)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def readline(self, newline_rx: str | None = None,
|
|
130
|
+
timeout: float | None = None) -> str:
|
|
131
|
+
"""Read a line from device.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
newline_rx : str, optional
|
|
136
|
+
Newline character denoting end of line read.
|
|
137
|
+
Defaults to self.newline_rx.
|
|
138
|
+
timeout : float, optional
|
|
139
|
+
Timeout.
|
|
140
|
+
Defaults to self.timeout.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
line : str
|
|
145
|
+
|
|
146
|
+
Examples
|
|
147
|
+
--------
|
|
148
|
+
>>> TBD
|
|
149
|
+
"""
|
|
150
|
+
newline_rx = self.newline_rx if newline_rx is None else newline_rx
|
|
151
|
+
timeout = self.timeout if timeout is None else timeout
|
|
152
|
+
|
|
153
|
+
nl = newline_rx.encode(self.encoding)
|
|
154
|
+
t0 = time.monotonic()
|
|
155
|
+
line_bytes = None
|
|
156
|
+
|
|
157
|
+
while True:
|
|
158
|
+
i = self._rx_buffer.find(nl)
|
|
159
|
+
if i != -1:
|
|
160
|
+
line_bytes = self._rx_buffer[:i]
|
|
161
|
+
del self._rx_buffer[:i + len(nl)]
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
if timeout is not None and time.monotonic() - t0 >= timeout:
|
|
165
|
+
if self._rx_buffer:
|
|
166
|
+
line_bytes = bytes(self._rx_buffer)
|
|
167
|
+
self._rx_buffer.clear()
|
|
168
|
+
else:
|
|
169
|
+
line_bytes = b''
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
n = self.ser.in_waiting
|
|
173
|
+
if n:
|
|
174
|
+
self._rx_buffer.extend(self.ser.read(n))
|
|
175
|
+
else:
|
|
176
|
+
time.sleep(0.005)
|
|
177
|
+
|
|
178
|
+
line = line_bytes.decode(self.encoding, errors='replace').strip()
|
|
179
|
+
if line:
|
|
180
|
+
log.info("RX: %r", line)
|
|
181
|
+
return line
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def query(self, command: str, **kwargs) -> str | list[str]:
|
|
185
|
+
"""Primary method for sending command and receiving response.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
command : str
|
|
190
|
+
Command sent to device.
|
|
191
|
+
|
|
192
|
+
**kwargs
|
|
193
|
+
Additional read options.
|
|
194
|
+
May override instance defaults such as `newline_tx`, `newline_rx`, etc.
|
|
195
|
+
|
|
196
|
+
newline_tx : str, optional
|
|
197
|
+
Newline character for `self.write`.
|
|
198
|
+
Defaults to `self.newline_tx`.
|
|
199
|
+
newline_rx : str, optional
|
|
200
|
+
Newline character for `self.readline`.
|
|
201
|
+
Defaults to `self.newline_rx`.
|
|
202
|
+
timeout : float, optional
|
|
203
|
+
Read timeout for whole query, separate from `self.ser.timeout`.
|
|
204
|
+
Defaults to `self.timeout`.
|
|
205
|
+
terminator : str, optional
|
|
206
|
+
Ends readline loop when `terminator` in `line`.
|
|
207
|
+
Defaults to `self.terminator`.
|
|
208
|
+
terminator_cmd : str, optional
|
|
209
|
+
Command string sent to port that triggers device to emit `terminator`.
|
|
210
|
+
Defaults to `self.terminator_cmd`.
|
|
211
|
+
strip_terminator : bool, optional
|
|
212
|
+
Remove `terminator` line from end of `response`.
|
|
213
|
+
Defaults to `True`.
|
|
214
|
+
terminator_delay : float, optional
|
|
215
|
+
Wait `terminator_delay` seconds before sending `terminator_cmd`.
|
|
216
|
+
Defaults to 0.
|
|
217
|
+
num_lines : int, optional
|
|
218
|
+
Stops read when `len(response) == num_lines`.
|
|
219
|
+
Defaults to None.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
response : str or list[str]
|
|
224
|
+
|
|
225
|
+
Examples
|
|
226
|
+
--------
|
|
227
|
+
>>> TBD
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
newline_tx = kwargs.get('newline_tx', self.newline_tx)
|
|
231
|
+
newline_rx = kwargs.get('newline_rx', self.newline_rx)
|
|
232
|
+
timeout = kwargs.get('timeout', self.timeout)
|
|
233
|
+
terminator = kwargs.get('terminator', self.terminator)
|
|
234
|
+
terminator_cmd = kwargs.get('terminator_cmd', self.terminator_cmd)
|
|
235
|
+
strip_terminator = kwargs.get('strip_terminator', True)
|
|
236
|
+
terminator_delay = kwargs.get('terminator_delay', 0)
|
|
237
|
+
num_lines = kwargs.get('num_lines', None)
|
|
238
|
+
|
|
239
|
+
# do we want to check if stuff is getting received?
|
|
240
|
+
|
|
241
|
+
self.flush()
|
|
242
|
+
|
|
243
|
+
self.write(command, newline_tx=newline_tx, append_newline=True)
|
|
244
|
+
|
|
245
|
+
response = []
|
|
246
|
+
t0 = time.monotonic()
|
|
247
|
+
sent_terminator = False
|
|
248
|
+
saw_terminator = False
|
|
249
|
+
|
|
250
|
+
while True:
|
|
251
|
+
# End read if timeout
|
|
252
|
+
if timeout is None:
|
|
253
|
+
remaining = None
|
|
254
|
+
else:
|
|
255
|
+
remaining = timeout - (time.monotonic() - t0)
|
|
256
|
+
if remaining <= 0:
|
|
257
|
+
break
|
|
258
|
+
|
|
259
|
+
# Send terminator_cmd after terminator_delay.
|
|
260
|
+
# Toggle sent_terminator to True so it only sends it once.
|
|
261
|
+
if (terminator_cmd is not None
|
|
262
|
+
and terminator_delay >= 0
|
|
263
|
+
and not sent_terminator
|
|
264
|
+
and time.monotonic() - t0 >= terminator_delay
|
|
265
|
+
):
|
|
266
|
+
self.write(terminator_cmd, newline_tx=newline_tx, append_newline=False)
|
|
267
|
+
sent_terminator = True
|
|
268
|
+
|
|
269
|
+
# Read line and append to response if it's not empty
|
|
270
|
+
line = self.readline(newline_rx=newline_rx, timeout=remaining)
|
|
271
|
+
|
|
272
|
+
if line:
|
|
273
|
+
response.append(line)
|
|
274
|
+
|
|
275
|
+
# Optionally end read at num_lines
|
|
276
|
+
if num_lines is not None and len(response) >= num_lines:
|
|
277
|
+
break
|
|
278
|
+
|
|
279
|
+
# Optionally end read at terminator
|
|
280
|
+
if (terminator is not None
|
|
281
|
+
and line
|
|
282
|
+
and terminator in line
|
|
283
|
+
and sent_terminator):
|
|
284
|
+
saw_terminator = True
|
|
285
|
+
break
|
|
286
|
+
|
|
287
|
+
# Optionally remove terminator from response.
|
|
288
|
+
if strip_terminator and response and terminator and saw_terminator and terminator in response[-1]:
|
|
289
|
+
response = response[:-1]
|
|
290
|
+
|
|
291
|
+
# If response is single line, return as str
|
|
292
|
+
if len(response) == 1:
|
|
293
|
+
response = response[0]
|
|
294
|
+
|
|
295
|
+
# clear 'self._rx_buffer'
|
|
296
|
+
self._rx_buffer = bytearray()
|
|
297
|
+
|
|
298
|
+
# flush
|
|
299
|
+
if self.ser.in_waiting != 0:
|
|
300
|
+
self.flush()
|
|
301
|
+
|
|
302
|
+
return response
|
|
303
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sertools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interface for serial devices using pyserial.
|
|
5
|
+
Author-email: Jason Doar <jbdoar@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jbdoar/sertools
|
|
8
|
+
Project-URL: Issues, https://github.com/jbdoar/sertools/issues
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pyserial
|
|
13
|
+
Requires-Dist: readchar
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# sertools
|
|
17
|
+
|
|
18
|
+
Interface for serial devices using pyserial.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install sertools
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
- Query method with flexible configuration parameters for various response formats.
|
|
28
|
+
- Lightweight serial port terminal emulator.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
The SerialDevice instance may be called with a command string to send to the serial port and optional readback/terminator parameters.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from sertools import SerialDevice
|
|
35
|
+
ser = SerialDevice()
|
|
36
|
+
ser('HELP')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Development
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/jbdoar/sertools
|
|
42
|
+
cd sertools
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/sertools/__init__.py
|
|
5
|
+
src/sertools/serial_device.py
|
|
6
|
+
src/sertools.egg-info/PKG-INFO
|
|
7
|
+
src/sertools.egg-info/SOURCES.txt
|
|
8
|
+
src/sertools.egg-info/dependency_links.txt
|
|
9
|
+
src/sertools.egg-info/entry_points.txt
|
|
10
|
+
src/sertools.egg-info/requires.txt
|
|
11
|
+
src/sertools.egg-info/top_level.txt
|
|
12
|
+
src/sertools/scripts/__init__.py
|
|
13
|
+
src/sertools/scripts/sterm.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sertools
|