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 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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ pyserial>=3.5
@@ -0,0 +1 @@
1
+ spl_spr_monitor