splspr 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.
- splspr-0.1.0/LICENSE +21 -0
- splspr-0.1.0/MANIFEST.in +1 -0
- splspr-0.1.0/PKG-INFO +73 -0
- splspr-0.1.0/README.md +59 -0
- splspr-0.1.0/examples/spl_spr_key_demo.py +41 -0
- splspr-0.1.0/pyproject.toml +21 -0
- splspr-0.1.0/setup.cfg +4 -0
- splspr-0.1.0/spl_spr_monitor.py +286 -0
- splspr-0.1.0/splspr.egg-info/PKG-INFO +73 -0
- splspr-0.1.0/splspr.egg-info/SOURCES.txt +11 -0
- splspr-0.1.0/splspr.egg-info/dependency_links.txt +1 -0
- splspr-0.1.0/splspr.egg-info/requires.txt +1 -0
- splspr-0.1.0/splspr.egg-info/top_level.txt +1 -0
splspr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cuber~
|
|
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.
|
splspr-0.1.0/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include examples/spl_spr_key_demo.py
|
splspr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: splspr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read SPL and SPR measurements from a compatible serial device
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Topic :: System :: Hardware
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pyserial>=3.5
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# splspr
|
|
16
|
+
|
|
17
|
+
Read SPL and SPR measurements from a compatible serial device in Python.
|
|
18
|
+
The module receives data in the background and can record it to CSV.
|
|
19
|
+
The library works without Vizard. A Vizard keyboard demo is included in the
|
|
20
|
+
source distribution under `examples/`.
|
|
21
|
+
|
|
22
|
+
SPL・SPR測定器から値を受信するPythonモジュールです。SPLとSPRを個別に取得でき、
|
|
23
|
+
CSV記録の開始・終了も個別に操作できます。
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
python -m pip install splspr
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Python 3.10 or later and a measurement device using the compatible serial
|
|
32
|
+
protocol are required. The default serial settings are `COM2`, 19200 baud,
|
|
33
|
+
8 data bits, no parity, and one stop bit. Set `port` to match your device.
|
|
34
|
+
For use in Vizard, install the package in Vizard's own Python environment.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import time
|
|
40
|
+
|
|
41
|
+
import spl_spr_monitor as monitor
|
|
42
|
+
|
|
43
|
+
if monitor.start(port="COM2") is None:
|
|
44
|
+
raise RuntimeError("Could not open the measurement device.")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
monitor.start.logging() # Optional
|
|
48
|
+
for _ in range(5):
|
|
49
|
+
time.sleep(0.1)
|
|
50
|
+
spl, spr = monitor.get_current_values()
|
|
51
|
+
if spl is not None:
|
|
52
|
+
print(f"SPL={spl:.3f} SPR={spr:.3f}")
|
|
53
|
+
monitor.stop_logging()
|
|
54
|
+
finally:
|
|
55
|
+
monitor.stop()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `start()` opens the serial connection and begins receiving samples.
|
|
59
|
+
- `get_spl()` and `get_spr()` return the latest values individually.
|
|
60
|
+
`get_current_values()` returns a pair from the same sample. Before the
|
|
61
|
+
first valid sample, the values are `None`.
|
|
62
|
+
- `start.logging()` starts CSV recording and returns the file path.
|
|
63
|
+
By default, the file is created beside the calling script. Pass
|
|
64
|
+
`csv_path` to choose another location.
|
|
65
|
+
- `stop_logging()` closes the CSV and keeps the connection open.
|
|
66
|
+
`stop()` closes both the serial connection and any active CSV.
|
|
67
|
+
|
|
68
|
+
CSV columns are `elapsed_time_sec`, `unix_time_sec`, `spl`, and `spr`.
|
|
69
|
+
The timestamps are assigned by the receiving computer.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT. See `LICENSE`.
|
splspr-0.1.0/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# splspr
|
|
2
|
+
|
|
3
|
+
Read SPL and SPR measurements from a compatible serial device in Python.
|
|
4
|
+
The module receives data in the background and can record it to CSV.
|
|
5
|
+
The library works without Vizard. A Vizard keyboard demo is included in the
|
|
6
|
+
source distribution under `examples/`.
|
|
7
|
+
|
|
8
|
+
SPL・SPR測定器から値を受信するPythonモジュールです。SPLとSPRを個別に取得でき、
|
|
9
|
+
CSV記録の開始・終了も個別に操作できます。
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
python -m pip install splspr
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Python 3.10 or later and a measurement device using the compatible serial
|
|
18
|
+
protocol are required. The default serial settings are `COM2`, 19200 baud,
|
|
19
|
+
8 data bits, no parity, and one stop bit. Set `port` to match your device.
|
|
20
|
+
For use in Vizard, install the package in Vizard's own Python environment.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
import spl_spr_monitor as monitor
|
|
28
|
+
|
|
29
|
+
if monitor.start(port="COM2") is None:
|
|
30
|
+
raise RuntimeError("Could not open the measurement device.")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
monitor.start.logging() # Optional
|
|
34
|
+
for _ in range(5):
|
|
35
|
+
time.sleep(0.1)
|
|
36
|
+
spl, spr = monitor.get_current_values()
|
|
37
|
+
if spl is not None:
|
|
38
|
+
print(f"SPL={spl:.3f} SPR={spr:.3f}")
|
|
39
|
+
monitor.stop_logging()
|
|
40
|
+
finally:
|
|
41
|
+
monitor.stop()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `start()` opens the serial connection and begins receiving samples.
|
|
45
|
+
- `get_spl()` and `get_spr()` return the latest values individually.
|
|
46
|
+
`get_current_values()` returns a pair from the same sample. Before the
|
|
47
|
+
first valid sample, the values are `None`.
|
|
48
|
+
- `start.logging()` starts CSV recording and returns the file path.
|
|
49
|
+
By default, the file is created beside the calling script. Pass
|
|
50
|
+
`csv_path` to choose another location.
|
|
51
|
+
- `stop_logging()` closes the CSV and keeps the connection open.
|
|
52
|
+
`stop()` closes both the serial connection and any active CSV.
|
|
53
|
+
|
|
54
|
+
CSV columns are `elapsed_time_sec`, `unix_time_sec`, `spl`, and `spr`.
|
|
55
|
+
The timestamps are assigned by the receiving computer.
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""VizardでAキーを押すとSPR、Sキーを押すとSPLを表示する。"""
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
|
|
5
|
+
import viz
|
|
6
|
+
import vizact
|
|
7
|
+
|
|
8
|
+
import spl_spr_monitor
|
|
9
|
+
|
|
10
|
+
def print_spr():
|
|
11
|
+
value = spl_spr_monitor.get_spr()
|
|
12
|
+
print('SPR: 未受信' if value is None else 'SPR: {:.3f}'.format(value))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def print_spl():
|
|
16
|
+
value = spl_spr_monitor.get_spl()
|
|
17
|
+
print('SPL: 未受信' if value is None else 'SPL: {:.3f}'.format(value))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def exit_program():
|
|
21
|
+
spl_spr_monitor.stop()
|
|
22
|
+
viz.quit()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main():
|
|
26
|
+
viz.go()
|
|
27
|
+
if spl_spr_monitor.start() is None:
|
|
28
|
+
print('SPL/SPRデバイスに接続できませんでした。COMポートを確認してください。')
|
|
29
|
+
viz.quit()
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
atexit.register(spl_spr_monitor.stop)
|
|
33
|
+
spl_spr_monitor.start.logging()
|
|
34
|
+
vizact.onkeydown('a', print_spr)
|
|
35
|
+
vizact.onkeydown('s', print_spl)
|
|
36
|
+
vizact.onkeydown(viz.KEY_ESCAPE, exit_program)
|
|
37
|
+
print('Aキー: SPR / Sキー: SPL / Escキー: 終了')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "splspr"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Read SPL and SPR measurements from a compatible serial device"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
dependencies = ["pyserial>=3.5"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Topic :: System :: Hardware",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
py-modules = ["spl_spr_monitor"]
|
splspr-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""シリアル接続から最新の SPL・SPR を取得するモジュール。"""
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
import serial
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _experiment_directory():
|
|
14
|
+
# Walk past this module's logging wrapper to the experiment script.
|
|
15
|
+
caller = sys._getframe(2)
|
|
16
|
+
while caller is not None and caller.f_globals is globals():
|
|
17
|
+
caller = caller.f_back
|
|
18
|
+
if caller is not None:
|
|
19
|
+
script = caller.f_globals.get('__file__') or caller.f_code.co_filename
|
|
20
|
+
if script and not script.startswith('<'):
|
|
21
|
+
return os.path.dirname(os.path.abspath(script))
|
|
22
|
+
|
|
23
|
+
main_script = getattr(sys.modules.get('__main__'), '__file__', None)
|
|
24
|
+
if main_script and not main_script.startswith('<'):
|
|
25
|
+
return os.path.dirname(os.path.abspath(main_script))
|
|
26
|
+
return os.getcwd()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SPLSPRMonitor:
|
|
30
|
+
PAYLOAD_LENGTH = 8
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
port='COM2',
|
|
35
|
+
baudrate=19200,
|
|
36
|
+
read_interval=None,
|
|
37
|
+
print_values=False,
|
|
38
|
+
csv_path=None,
|
|
39
|
+
csv_flush=True,
|
|
40
|
+
):
|
|
41
|
+
if read_interval is not None and read_interval <= 0:
|
|
42
|
+
raise ValueError('read_interval must be positive')
|
|
43
|
+
self.port = port
|
|
44
|
+
self.baudrate = baudrate
|
|
45
|
+
self.read_interval = read_interval
|
|
46
|
+
self.print_values = print_values
|
|
47
|
+
self.csv_path = csv_path
|
|
48
|
+
self.csv_flush = csv_flush
|
|
49
|
+
self._latest = (None, None)
|
|
50
|
+
self._serial_buffer = ''
|
|
51
|
+
self._stop_event = threading.Event()
|
|
52
|
+
self._lock = threading.Lock()
|
|
53
|
+
self._log_lock = threading.Lock()
|
|
54
|
+
self._log_file = None
|
|
55
|
+
self._log_writer = None
|
|
56
|
+
self._log_started_at = None
|
|
57
|
+
self._reader = None
|
|
58
|
+
|
|
59
|
+
def start(self):
|
|
60
|
+
"""シリアルポートを開いて受信を開始する。接続に失敗したら False を返す。"""
|
|
61
|
+
with self._lock:
|
|
62
|
+
if self._reader is not None and self._reader.is_alive():
|
|
63
|
+
return True
|
|
64
|
+
try:
|
|
65
|
+
connection = serial.Serial(
|
|
66
|
+
port=self.port,
|
|
67
|
+
baudrate=self.baudrate,
|
|
68
|
+
bytesize=serial.EIGHTBITS,
|
|
69
|
+
parity=serial.PARITY_NONE,
|
|
70
|
+
stopbits=serial.STOPBITS_ONE,
|
|
71
|
+
timeout=0.1,
|
|
72
|
+
)
|
|
73
|
+
except serial.SerialException as error:
|
|
74
|
+
print('SPR serial open failed: {}'.format(error))
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
self._latest = (None, None)
|
|
79
|
+
self._serial_buffer = ''
|
|
80
|
+
self._stop_event.clear()
|
|
81
|
+
self._reader = threading.Thread(
|
|
82
|
+
target=self._receive,
|
|
83
|
+
args=(connection,),
|
|
84
|
+
name='SPL-SPR receiver',
|
|
85
|
+
daemon=True,
|
|
86
|
+
)
|
|
87
|
+
self._reader.start()
|
|
88
|
+
except BaseException:
|
|
89
|
+
connection.close()
|
|
90
|
+
raise
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def stop(self):
|
|
94
|
+
"""受信を停止し、シリアルポートとCSVを閉じる。"""
|
|
95
|
+
with self._lock:
|
|
96
|
+
if self._reader is not None:
|
|
97
|
+
self._stop_event.set()
|
|
98
|
+
self._reader.join()
|
|
99
|
+
self._reader = None
|
|
100
|
+
self._latest = (None, None)
|
|
101
|
+
|
|
102
|
+
def start_logging(self, csv_path=None):
|
|
103
|
+
"""Start CSV recording and return its path."""
|
|
104
|
+
with self._lock, self._log_lock:
|
|
105
|
+
if self._reader is None or not self._reader.is_alive() or self._stop_event.is_set():
|
|
106
|
+
raise RuntimeError('Call start() before starting CSV recording.')
|
|
107
|
+
if self._log_file is not None:
|
|
108
|
+
return self._log_file.name
|
|
109
|
+
|
|
110
|
+
path = self.csv_path if csv_path is None else csv_path
|
|
111
|
+
if path is None:
|
|
112
|
+
path = datetime.now().strftime('spl_spr_log_%Y%m%d_%H%M%S_%f.csv')
|
|
113
|
+
if not os.path.isabs(path):
|
|
114
|
+
path = os.path.join(_experiment_directory(), path)
|
|
115
|
+
log_dir = os.path.dirname(path)
|
|
116
|
+
if log_dir:
|
|
117
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
118
|
+
log_file = open(path, 'w', newline='')
|
|
119
|
+
try:
|
|
120
|
+
writer = csv.writer(log_file)
|
|
121
|
+
writer.writerow(['elapsed_time_sec', 'unix_time_sec', 'spl', 'spr'])
|
|
122
|
+
if self.csv_flush:
|
|
123
|
+
log_file.flush()
|
|
124
|
+
except BaseException:
|
|
125
|
+
log_file.close()
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
self._log_file = log_file
|
|
129
|
+
self._log_writer = writer
|
|
130
|
+
self._log_started_at = time.monotonic()
|
|
131
|
+
return log_file.name
|
|
132
|
+
|
|
133
|
+
def stop_logging(self):
|
|
134
|
+
"""Close the CSV log while keeping serial reception active."""
|
|
135
|
+
with self._lock:
|
|
136
|
+
self._close_log()
|
|
137
|
+
|
|
138
|
+
def _close_log(self):
|
|
139
|
+
with self._log_lock:
|
|
140
|
+
log_file = self._log_file
|
|
141
|
+
self._log_file = None
|
|
142
|
+
self._log_writer = None
|
|
143
|
+
if log_file is not None:
|
|
144
|
+
log_file.close()
|
|
145
|
+
|
|
146
|
+
def get_spl(self):
|
|
147
|
+
"""最新の SPL を返す。有効な受信値がなければ None。"""
|
|
148
|
+
return self._latest[0]
|
|
149
|
+
|
|
150
|
+
def get_spr(self):
|
|
151
|
+
"""最新の SPR を返す。有効な受信値がなければ None。"""
|
|
152
|
+
return self._latest[1]
|
|
153
|
+
|
|
154
|
+
def get_values(self):
|
|
155
|
+
"""同じ受信フレームの (SPL, SPR) を返す。"""
|
|
156
|
+
return self._latest
|
|
157
|
+
|
|
158
|
+
def _receive(self, connection):
|
|
159
|
+
try:
|
|
160
|
+
while not self._stop_event.is_set():
|
|
161
|
+
waiting = connection.in_waiting
|
|
162
|
+
if self.read_interval is None:
|
|
163
|
+
# Block for the first byte when the receive buffer is empty.
|
|
164
|
+
waiting = max(1, waiting)
|
|
165
|
+
if waiting:
|
|
166
|
+
raw = connection.read(waiting).decode('ascii', errors='ignore')
|
|
167
|
+
for spl, spr in self._consume(raw):
|
|
168
|
+
self._latest = (spl, spr)
|
|
169
|
+
with self._log_lock:
|
|
170
|
+
if self._log_writer is not None:
|
|
171
|
+
elapsed = time.monotonic() - self._log_started_at
|
|
172
|
+
self._log_writer.writerow([
|
|
173
|
+
'{:.6f}'.format(value)
|
|
174
|
+
for value in (elapsed, time.time(), spl, spr)
|
|
175
|
+
])
|
|
176
|
+
if self.csv_flush:
|
|
177
|
+
self._log_file.flush()
|
|
178
|
+
if self.print_values:
|
|
179
|
+
print('SPL:{:7.3f} SPR:{:7.3f}'.format(spl, spr))
|
|
180
|
+
if self.read_interval is not None:
|
|
181
|
+
self._stop_event.wait(self.read_interval)
|
|
182
|
+
except (serial.SerialException, OSError) as error:
|
|
183
|
+
print('SPL/SPR receive failed: {}'.format(error))
|
|
184
|
+
finally:
|
|
185
|
+
self._stop_event.set()
|
|
186
|
+
self._latest = (None, None)
|
|
187
|
+
try:
|
|
188
|
+
connection.close()
|
|
189
|
+
finally:
|
|
190
|
+
self._close_log()
|
|
191
|
+
|
|
192
|
+
def _consume(self, raw):
|
|
193
|
+
self._serial_buffer += raw
|
|
194
|
+
while True:
|
|
195
|
+
start = self._serial_buffer.find(':')
|
|
196
|
+
if start == -1:
|
|
197
|
+
self._serial_buffer = ''
|
|
198
|
+
return
|
|
199
|
+
self._serial_buffer = self._serial_buffer[start:]
|
|
200
|
+
if len(self._serial_buffer) < self.PAYLOAD_LENGTH:
|
|
201
|
+
return
|
|
202
|
+
next_start = self._serial_buffer.find(':', 1, self.PAYLOAD_LENGTH)
|
|
203
|
+
if next_start != -1:
|
|
204
|
+
self._serial_buffer = self._serial_buffer[next_start:]
|
|
205
|
+
continue
|
|
206
|
+
# Only consume the measurement prefix. The next ':' delimits
|
|
207
|
+
# another sample even when several samples arrive in one read.
|
|
208
|
+
payload = self._serial_buffer[:self.PAYLOAD_LENGTH]
|
|
209
|
+
self._serial_buffer = self._serial_buffer[self.PAYLOAD_LENGTH:]
|
|
210
|
+
values = self.parse_frame(payload)
|
|
211
|
+
if values is not None:
|
|
212
|
+
yield values
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def parse_frame(frame):
|
|
216
|
+
try:
|
|
217
|
+
spl_raw = int(frame[1:4], 16)
|
|
218
|
+
spr_raw = int(frame[5:8], 16)
|
|
219
|
+
except (TypeError, ValueError):
|
|
220
|
+
return None
|
|
221
|
+
if spl_raw >= 0xC00:
|
|
222
|
+
spl_raw -= 0x1000
|
|
223
|
+
if spr_raw >= 0xC00:
|
|
224
|
+
spr_raw -= 0x1000
|
|
225
|
+
return (spl_raw * 2.0) / 1000.0, (spr_raw * 2.0) / 1000.0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
_default_monitor = None
|
|
229
|
+
_default_lock = threading.Lock()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def start(**kwargs):
|
|
233
|
+
"""既定のモニターを開始する。接続に失敗したら None を返す。"""
|
|
234
|
+
global _default_monitor
|
|
235
|
+
with _default_lock:
|
|
236
|
+
if _default_monitor is None:
|
|
237
|
+
monitor = SPLSPRMonitor(**kwargs)
|
|
238
|
+
else:
|
|
239
|
+
monitor = _default_monitor
|
|
240
|
+
if monitor.start():
|
|
241
|
+
_default_monitor = monitor
|
|
242
|
+
return monitor
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def start_logging(csv_path=None):
|
|
247
|
+
"""Start CSV recording for the default monitor."""
|
|
248
|
+
with _default_lock:
|
|
249
|
+
if _default_monitor is None:
|
|
250
|
+
raise RuntimeError('Call start() before starting CSV recording.')
|
|
251
|
+
return _default_monitor.start_logging(csv_path)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
start.logging = start_logging
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def stop():
|
|
258
|
+
"""既定のモニターを停止する。"""
|
|
259
|
+
global _default_monitor
|
|
260
|
+
with _default_lock:
|
|
261
|
+
if _default_monitor is not None:
|
|
262
|
+
_default_monitor.stop()
|
|
263
|
+
_default_monitor = None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def stop_logging():
|
|
267
|
+
"""Close the default monitor's CSV log while keeping reception active."""
|
|
268
|
+
with _default_lock:
|
|
269
|
+
if _default_monitor is not None:
|
|
270
|
+
_default_monitor.stop_logging()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def get_current_values():
|
|
274
|
+
"""既定のモニターから、同じ受信フレームの (SPL, SPR) を取得する。"""
|
|
275
|
+
monitor = _default_monitor
|
|
276
|
+
return monitor.get_values() if monitor is not None else (None, None)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def get_spl():
|
|
280
|
+
"""既定のモニターから最新の SPL を取得する。"""
|
|
281
|
+
return get_current_values()[0]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def get_spr():
|
|
285
|
+
"""既定のモニターから最新の SPR を取得する。"""
|
|
286
|
+
return get_current_values()[1]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: splspr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read SPL and SPR measurements from a compatible serial device
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
8
|
+
Classifier: Topic :: System :: Hardware
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pyserial>=3.5
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# splspr
|
|
16
|
+
|
|
17
|
+
Read SPL and SPR measurements from a compatible serial device in Python.
|
|
18
|
+
The module receives data in the background and can record it to CSV.
|
|
19
|
+
The library works without Vizard. A Vizard keyboard demo is included in the
|
|
20
|
+
source distribution under `examples/`.
|
|
21
|
+
|
|
22
|
+
SPL・SPR測定器から値を受信するPythonモジュールです。SPLとSPRを個別に取得でき、
|
|
23
|
+
CSV記録の開始・終了も個別に操作できます。
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
python -m pip install splspr
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Python 3.10 or later and a measurement device using the compatible serial
|
|
32
|
+
protocol are required. The default serial settings are `COM2`, 19200 baud,
|
|
33
|
+
8 data bits, no parity, and one stop bit. Set `port` to match your device.
|
|
34
|
+
For use in Vizard, install the package in Vizard's own Python environment.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import time
|
|
40
|
+
|
|
41
|
+
import spl_spr_monitor as monitor
|
|
42
|
+
|
|
43
|
+
if monitor.start(port="COM2") is None:
|
|
44
|
+
raise RuntimeError("Could not open the measurement device.")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
monitor.start.logging() # Optional
|
|
48
|
+
for _ in range(5):
|
|
49
|
+
time.sleep(0.1)
|
|
50
|
+
spl, spr = monitor.get_current_values()
|
|
51
|
+
if spl is not None:
|
|
52
|
+
print(f"SPL={spl:.3f} SPR={spr:.3f}")
|
|
53
|
+
monitor.stop_logging()
|
|
54
|
+
finally:
|
|
55
|
+
monitor.stop()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `start()` opens the serial connection and begins receiving samples.
|
|
59
|
+
- `get_spl()` and `get_spr()` return the latest values individually.
|
|
60
|
+
`get_current_values()` returns a pair from the same sample. Before the
|
|
61
|
+
first valid sample, the values are `None`.
|
|
62
|
+
- `start.logging()` starts CSV recording and returns the file path.
|
|
63
|
+
By default, the file is created beside the calling script. Pass
|
|
64
|
+
`csv_path` to choose another location.
|
|
65
|
+
- `stop_logging()` closes the CSV and keeps the connection open.
|
|
66
|
+
`stop()` closes both the serial connection and any active CSV.
|
|
67
|
+
|
|
68
|
+
CSV columns are `elapsed_time_sec`, `unix_time_sec`, `spl`, and `spr`.
|
|
69
|
+
The timestamps are assigned by the receiving computer.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
spl_spr_monitor.py
|
|
6
|
+
examples/spl_spr_key_demo.py
|
|
7
|
+
splspr.egg-info/PKG-INFO
|
|
8
|
+
splspr.egg-info/SOURCES.txt
|
|
9
|
+
splspr.egg-info/dependency_links.txt
|
|
10
|
+
splspr.egg-info/requires.txt
|
|
11
|
+
splspr.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyserial>=3.5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spl_spr_monitor
|