teraflash-ctrl 1.4.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.
- teraflash_ctrl-1.4.0/PKG-INFO +87 -0
- teraflash_ctrl-1.4.0/README.md +72 -0
- teraflash_ctrl-1.4.0/pyproject.toml +26 -0
- teraflash_ctrl-1.4.0/setup.cfg +4 -0
- teraflash_ctrl-1.4.0/src/__init__.py +8 -0
- teraflash_ctrl-1.4.0/src/interface.py +356 -0
- teraflash_ctrl-1.4.0/src/io.py +139 -0
- teraflash_ctrl-1.4.0/src/math_utils.py +115 -0
- teraflash_ctrl-1.4.0/src/pulse_detection.py +37 -0
- teraflash_ctrl-1.4.0/src/teraflash.py +561 -0
- teraflash_ctrl-1.4.0/src/teraflash_ctrl.egg-info/PKG-INFO +87 -0
- teraflash_ctrl-1.4.0/src/teraflash_ctrl.egg-info/SOURCES.txt +13 -0
- teraflash_ctrl-1.4.0/src/teraflash_ctrl.egg-info/dependency_links.txt +1 -0
- teraflash_ctrl-1.4.0/src/teraflash_ctrl.egg-info/requires.txt +2 -0
- teraflash_ctrl-1.4.0/src/teraflash_ctrl.egg-info/top_level.txt +6 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: teraflash-ctrl
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: This Python package allows the configuration and readout of the TeraFlash Pro THz spectrometer.
|
|
5
|
+
Author-email: Linus Leo Stöckli <linus.stoeckli@unibe.ch>
|
|
6
|
+
Project-URL: Homepage, https://github.com/unibe-icelab/teraflash-ctrl-python
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/unibe-icelab/teraflash-ctrl-python/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: scipy
|
|
15
|
+
|
|
16
|
+
# TeraFlash Pro Python Package
|
|
17
|
+
|
|
18
|
+
<a href="https://github.com/unibe-icelab/teraflash-ctrl-python/releases"><img src="icons/icon.png" alt=“” width="100" height="100"> </img> </a>
|
|
19
|
+
|
|
20
|
+
This code is developed by the University of Bern and is no official product of Toptica Photonics AG.
|
|
21
|
+
This Python Package allows the configuration and readout of the TeraFlash Pro THz spectrometer.
|
|
22
|
+
The TCP communication protocol is probably incomplete and features may be missing as it was reverse engineered using
|
|
23
|
+
wireshark. The scanning stage is not supported but a list of other features is.
|
|
24
|
+
|
|
25
|
+
This is a simple library with no persistence settings (the configurations of the previous run will not be stored when the python session is closed).
|
|
26
|
+
A complete GUI written in Rust is also available [here](https://github.com/unibe-icelab/teraflash-ctrl).
|
|
27
|
+
|
|
28
|
+
Features:
|
|
29
|
+
- [x] Select begin time for the time window
|
|
30
|
+
- [x] Select range
|
|
31
|
+
- [x] Select average
|
|
32
|
+
- [x] Start/Stop Laser
|
|
33
|
+
- [x] Start/Stop Emitters
|
|
34
|
+
- [x] Start/Stop Acquisition
|
|
35
|
+
- [x] Set transmission
|
|
36
|
+
- [x] Set motion mode
|
|
37
|
+
- [x] Set channel
|
|
38
|
+
- [x] Get status
|
|
39
|
+
- [x] Get data (time domain and frequency domain)
|
|
40
|
+
- [x] auto pulse detection function
|
|
41
|
+
- [X] Set antenna range
|
|
42
|
+
- [ ] ...
|
|
43
|
+
|
|
44
|
+
## I. Installation
|
|
45
|
+
Download the [latest release](https://github.com/unibe-icelab/teraflash-ctrl-python/releases) or the [current state](https://github.com/unibe-icelab/teraflash-ctrl-python/archive/refs/heads/main.zip)
|
|
46
|
+
as a zip archive, move it into your working directory and then run:
|
|
47
|
+
```shell
|
|
48
|
+
pip install teraflash-ctrl-python-v1.4.0.zip
|
|
49
|
+
```
|
|
50
|
+
for the latest version or
|
|
51
|
+
```shell
|
|
52
|
+
pip install teraflash-ctrl-python-main.zip
|
|
53
|
+
```
|
|
54
|
+
for the current state of the main branch to install the package for your virtual python environment or system-wide.
|
|
55
|
+
|
|
56
|
+
## II. Taking a measurement
|
|
57
|
+
When connected to the device, you can turn on the laser and then the emitter.
|
|
58
|
+
After starting the acquisition, new data should be continuously updated and the most recent dataset can be obtained using `device.get_data()`:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from teraflash import TeraFlash
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
ip = "169.254.84.101"
|
|
65
|
+
with TeraFlash(ip) as device:
|
|
66
|
+
print(device.get_status())
|
|
67
|
+
device.set_laser(True)
|
|
68
|
+
device.set_emitter(1, True)
|
|
69
|
+
device.set_acq_start()
|
|
70
|
+
print(device.get_data())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Always use the context manager to ensure that the connection is properly closed upon exiting!
|
|
74
|
+
Consult the [`example.py`](example.py) for usage.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_Disclaimer: This package is provided on a best effort basis with no guarantee as to the functionality and correctness. Use at your
|
|
78
|
+
own risk.
|
|
79
|
+
Users are encouraged to contribute by submitting [issues](https://github.com/unibe-icelab/teraflash-ctrl-python/issues) and/or [pull requests](https://github.com/unibe-icelab/teraflash-ctrl-python/pulls) for bug reporting or feature requests._
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Copyright (c) 2026 University of Bern, Space Research & Planetary Sciences, Linus Leo Stöckli.
|
|
83
|
+
|
|
84
|
+
This work is licensed under the Creative Commons
|
|
85
|
+
Attribution-NonCommercial 4.0 International License.
|
|
86
|
+
To view a copy of this license, visit
|
|
87
|
+
https://creativecommons.org/licenses/by-nc/4.0/
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# TeraFlash Pro Python Package
|
|
2
|
+
|
|
3
|
+
<a href="https://github.com/unibe-icelab/teraflash-ctrl-python/releases"><img src="icons/icon.png" alt=“” width="100" height="100"> </img> </a>
|
|
4
|
+
|
|
5
|
+
This code is developed by the University of Bern and is no official product of Toptica Photonics AG.
|
|
6
|
+
This Python Package allows the configuration and readout of the TeraFlash Pro THz spectrometer.
|
|
7
|
+
The TCP communication protocol is probably incomplete and features may be missing as it was reverse engineered using
|
|
8
|
+
wireshark. The scanning stage is not supported but a list of other features is.
|
|
9
|
+
|
|
10
|
+
This is a simple library with no persistence settings (the configurations of the previous run will not be stored when the python session is closed).
|
|
11
|
+
A complete GUI written in Rust is also available [here](https://github.com/unibe-icelab/teraflash-ctrl).
|
|
12
|
+
|
|
13
|
+
Features:
|
|
14
|
+
- [x] Select begin time for the time window
|
|
15
|
+
- [x] Select range
|
|
16
|
+
- [x] Select average
|
|
17
|
+
- [x] Start/Stop Laser
|
|
18
|
+
- [x] Start/Stop Emitters
|
|
19
|
+
- [x] Start/Stop Acquisition
|
|
20
|
+
- [x] Set transmission
|
|
21
|
+
- [x] Set motion mode
|
|
22
|
+
- [x] Set channel
|
|
23
|
+
- [x] Get status
|
|
24
|
+
- [x] Get data (time domain and frequency domain)
|
|
25
|
+
- [x] auto pulse detection function
|
|
26
|
+
- [X] Set antenna range
|
|
27
|
+
- [ ] ...
|
|
28
|
+
|
|
29
|
+
## I. Installation
|
|
30
|
+
Download the [latest release](https://github.com/unibe-icelab/teraflash-ctrl-python/releases) or the [current state](https://github.com/unibe-icelab/teraflash-ctrl-python/archive/refs/heads/main.zip)
|
|
31
|
+
as a zip archive, move it into your working directory and then run:
|
|
32
|
+
```shell
|
|
33
|
+
pip install teraflash-ctrl-python-v1.4.0.zip
|
|
34
|
+
```
|
|
35
|
+
for the latest version or
|
|
36
|
+
```shell
|
|
37
|
+
pip install teraflash-ctrl-python-main.zip
|
|
38
|
+
```
|
|
39
|
+
for the current state of the main branch to install the package for your virtual python environment or system-wide.
|
|
40
|
+
|
|
41
|
+
## II. Taking a measurement
|
|
42
|
+
When connected to the device, you can turn on the laser and then the emitter.
|
|
43
|
+
After starting the acquisition, new data should be continuously updated and the most recent dataset can be obtained using `device.get_data()`:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from teraflash import TeraFlash
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
ip = "169.254.84.101"
|
|
50
|
+
with TeraFlash(ip) as device:
|
|
51
|
+
print(device.get_status())
|
|
52
|
+
device.set_laser(True)
|
|
53
|
+
device.set_emitter(1, True)
|
|
54
|
+
device.set_acq_start()
|
|
55
|
+
print(device.get_data())
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Always use the context manager to ensure that the connection is properly closed upon exiting!
|
|
59
|
+
Consult the [`example.py`](example.py) for usage.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
_Disclaimer: This package is provided on a best effort basis with no guarantee as to the functionality and correctness. Use at your
|
|
63
|
+
own risk.
|
|
64
|
+
Users are encouraged to contribute by submitting [issues](https://github.com/unibe-icelab/teraflash-ctrl-python/issues) and/or [pull requests](https://github.com/unibe-icelab/teraflash-ctrl-python/pulls) for bug reporting or feature requests._
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Copyright (c) 2026 University of Bern, Space Research & Planetary Sciences, Linus Leo Stöckli.
|
|
68
|
+
|
|
69
|
+
This work is licensed under the Creative Commons
|
|
70
|
+
Attribution-NonCommercial 4.0 International License.
|
|
71
|
+
To view a copy of this license, visit
|
|
72
|
+
https://creativecommons.org/licenses/by-nc/4.0/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "teraflash-ctrl"
|
|
7
|
+
version = "1.4.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Linus Leo Stöckli", email = "linus.stoeckli@unibe.ch" },
|
|
10
|
+
]
|
|
11
|
+
description = "This Python package allows the configuration and readout of the TeraFlash Pro THz spectrometer."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
requires-python = ">=3.7"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: Other/Proprietary License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"numpy",
|
|
22
|
+
"scipy"
|
|
23
|
+
]
|
|
24
|
+
[project.urls]
|
|
25
|
+
"Homepage" = "https://github.com/unibe-icelab/teraflash-ctrl-python"
|
|
26
|
+
"Bug Tracker" = "https://github.com/unibe-icelab/teraflash-ctrl-python/issues"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2026 University of Bern, Space Research & Planetary Sciences, Linus Leo Stöckli.
|
|
3
|
+
|
|
4
|
+
This work is licensed under the Creative Commons
|
|
5
|
+
Attribution-NonCommercial 4.0 International License.
|
|
6
|
+
To view a copy of this license, visit
|
|
7
|
+
https://creativecommons.org/licenses/by-nc/4.0/
|
|
8
|
+
"""
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2026 University of Bern, Space Research & Planetary Sciences, Linus Leo Stöckli.
|
|
3
|
+
|
|
4
|
+
This work is licensed under the Creative Commons
|
|
5
|
+
Attribution-NonCommercial 4.0 International License.
|
|
6
|
+
To view a copy of this license, visit
|
|
7
|
+
https://creativecommons.org/licenses/by-nc/4.0/
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from queue import Queue, Empty
|
|
12
|
+
import threading
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import platform
|
|
16
|
+
import socket
|
|
17
|
+
import subprocess
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
from math_utils import get_fft
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DataContainer:
|
|
24
|
+
|
|
25
|
+
def __init__(self, n=1000):
|
|
26
|
+
"""
|
|
27
|
+
Data container that stores the data sent by the instrument
|
|
28
|
+
Args:
|
|
29
|
+
n: length of dataset (depends on the selected range)
|
|
30
|
+
"""
|
|
31
|
+
self.time = np.zeros(n)
|
|
32
|
+
self.freq = np.zeros(n)
|
|
33
|
+
|
|
34
|
+
self.signal_1 = np.zeros(n)
|
|
35
|
+
self.fft_1_amp = np.zeros(n)
|
|
36
|
+
self.fft_1_phase = np.zeros(n)
|
|
37
|
+
|
|
38
|
+
self.signal_2 = np.zeros(n)
|
|
39
|
+
self.fft_2_amp = np.zeros(n)
|
|
40
|
+
self.fft_2_phase = np.zeros(n)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# global variables for thread communication
|
|
44
|
+
data = DataContainer()
|
|
45
|
+
n_avg = 0
|
|
46
|
+
status = ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TopticaSocket:
|
|
50
|
+
def __init__(self,
|
|
51
|
+
ip: str,
|
|
52
|
+
running: threading.Event,
|
|
53
|
+
connected: threading.Event,
|
|
54
|
+
cmd_ack: threading.Event,
|
|
55
|
+
buffer_emptied: threading.Event,
|
|
56
|
+
range_changed: threading.Event,
|
|
57
|
+
acq_running: threading.Event,
|
|
58
|
+
avg_data: threading.Event):
|
|
59
|
+
"""
|
|
60
|
+
TCP Socket struct, used for the communication between the server (Computer) and the client (instrument)
|
|
61
|
+
with two TCP connections (one for configuration and one for data transmission)
|
|
62
|
+
Args:
|
|
63
|
+
ip: ip address
|
|
64
|
+
running: threading event that signals if the application is running
|
|
65
|
+
connected: threading event that signals if a client is connected
|
|
66
|
+
cmd_ack: threading event that signals when a command is acknowledged
|
|
67
|
+
range_changed: threading event that signals when the range has been changed in the configuration
|
|
68
|
+
"""
|
|
69
|
+
self.send_header = b'\xcd\xef\x124x\x9a\xfe\xdc\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'
|
|
70
|
+
self.r_stat_header = b'\xcd\xef\x124x\x9a\xfe\xdc\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x02'
|
|
71
|
+
self.r_dat_header = b'\xcd\xef\x124x\x9a\xfe\xdc\x00\x00\x00\x01\x00\t\x1a\xe6\x03\xe8\x00\x00\x04L\x00\x00\x0c\xcc\xcc\xcc\x00\x00\x16\x0b\x00\x00]\xd8\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00'
|
|
72
|
+
self.r_dat_header = b'\xcd\xef\x124x\x9a\xfe'
|
|
73
|
+
self.full_data_header_len = 52
|
|
74
|
+
self.read_header_len = 19
|
|
75
|
+
self.data_header_len = 7
|
|
76
|
+
|
|
77
|
+
self.avg_countdown = 0
|
|
78
|
+
|
|
79
|
+
self.config_server_address = (ip, 6341)
|
|
80
|
+
self.data_server_address = (ip, 6342)
|
|
81
|
+
|
|
82
|
+
self.t_begin = 1000.00
|
|
83
|
+
self.range = 50
|
|
84
|
+
self.antenna_range = 1000.0
|
|
85
|
+
|
|
86
|
+
self.running = running
|
|
87
|
+
self.connected = connected
|
|
88
|
+
self.cmd_ack = cmd_ack
|
|
89
|
+
self.range_changed = range_changed
|
|
90
|
+
self.buffer_emptied = buffer_emptied
|
|
91
|
+
self.acq_running = acq_running
|
|
92
|
+
self.avg_data = avg_data
|
|
93
|
+
|
|
94
|
+
if not self.ping(ip):
|
|
95
|
+
raise ConnectionError
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def ping(host: str):
|
|
99
|
+
"""
|
|
100
|
+
determines whether a device is connected or not
|
|
101
|
+
Args:
|
|
102
|
+
host: IP address as a string
|
|
103
|
+
|
|
104
|
+
Returns: True for connected, False for not connected
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
res = False
|
|
108
|
+
ping_param = "-n" if platform.system().lower() == "windows" else "-c"
|
|
109
|
+
command = ['ping', ping_param, '1', host]
|
|
110
|
+
result = subprocess.call(command)
|
|
111
|
+
if result == 0:
|
|
112
|
+
res = True
|
|
113
|
+
return res
|
|
114
|
+
|
|
115
|
+
def wait_for_answer(self, client: socket, length: int = 1024):
|
|
116
|
+
"""
|
|
117
|
+
waits for the device to acknowledge the previously sent command
|
|
118
|
+
Args:
|
|
119
|
+
client: socket object
|
|
120
|
+
length: length of the buffer
|
|
121
|
+
|
|
122
|
+
Returns: True for valid response, False for error
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
while self.running.is_set():
|
|
126
|
+
_data = client.recv(length)[self.read_header_len:]
|
|
127
|
+
if _data:
|
|
128
|
+
_data_decoded = _data.decode("utf-8", "ignore")
|
|
129
|
+
if "OK" in _data_decoded:
|
|
130
|
+
logging.debug(f"[TCP CONF] received: {_data_decoded}")
|
|
131
|
+
return True
|
|
132
|
+
elif "MON" in _data_decoded:
|
|
133
|
+
return True
|
|
134
|
+
logging.debug(f"[TCP CONF] received: {_data_decoded}")
|
|
135
|
+
else:
|
|
136
|
+
logging.error(f"[TCP CONF] No valid reply from device: {_data.decode('utf-8', 'ignore')}")
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def run_conf_tcp(self, cmd_queue: Queue):
|
|
140
|
+
"""
|
|
141
|
+
Config TCP thread on port 6341. This handles all the configurations for the device.
|
|
142
|
+
Args:
|
|
143
|
+
cmd_queue: Queue from main thread with all the interactive
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
global status
|
|
149
|
+
global data
|
|
150
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
151
|
+
# Set the SO_REUSEADDR option to allow reuse of the port
|
|
152
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
153
|
+
|
|
154
|
+
# bind server to the address (only works when the address exists)
|
|
155
|
+
s.bind(self.config_server_address)
|
|
156
|
+
logging.info(f"[TCP CONF] Starting server at address {self.config_server_address}")
|
|
157
|
+
# wait for connections
|
|
158
|
+
s.listen()
|
|
159
|
+
# accept connection from device
|
|
160
|
+
client, addr = s.accept()
|
|
161
|
+
logging.info(f"[TCP CONF] Connected by client with address {addr}")
|
|
162
|
+
# now we are connected
|
|
163
|
+
self.connected.set()
|
|
164
|
+
|
|
165
|
+
while self.running.is_set():
|
|
166
|
+
# check the queue for commands
|
|
167
|
+
try:
|
|
168
|
+
cmd = cmd_queue.get(block=False)
|
|
169
|
+
logging.debug(f"[TCP CONF] sending: {cmd}")
|
|
170
|
+
(b, c) = cmd
|
|
171
|
+
message = self.send_header + b + c.encode()
|
|
172
|
+
# send command
|
|
173
|
+
client.send(message)
|
|
174
|
+
if b == b'\x14':
|
|
175
|
+
# if we request the status, save the response
|
|
176
|
+
status = client.recv(2048)[self.read_header_len:].decode("utf-8", "ignore")
|
|
177
|
+
self.cmd_ack.set()
|
|
178
|
+
elif "RANGE" in c:
|
|
179
|
+
# if we change the range, also change it for the data thread
|
|
180
|
+
parts = c.split(" ")
|
|
181
|
+
self.range = float(parts[-1])
|
|
182
|
+
data.time = np.linspace(self.t_begin, self.t_begin + self.range, 20 * int(self.range) + 1)
|
|
183
|
+
# wait for acknowledge
|
|
184
|
+
if not self.wait_for_answer(client):
|
|
185
|
+
return
|
|
186
|
+
self.cmd_ack.set()
|
|
187
|
+
elif "BEGIN" in c:
|
|
188
|
+
# if we change the range, also change it for the data thread
|
|
189
|
+
parts = c.split(" ")
|
|
190
|
+
self.t_begin = float(parts[-1])
|
|
191
|
+
data.time = np.linspace(self.t_begin, self.t_begin + self.range, 20 * int(self.range) + 1)
|
|
192
|
+
# wait for acknowledge
|
|
193
|
+
if not self.wait_for_answer(client):
|
|
194
|
+
return
|
|
195
|
+
self.cmd_ack.set()
|
|
196
|
+
else:
|
|
197
|
+
# wait for acknowledge
|
|
198
|
+
if not self.wait_for_answer(client):
|
|
199
|
+
return
|
|
200
|
+
self.cmd_ack.set()
|
|
201
|
+
except Empty:
|
|
202
|
+
# just to a simple heartbeat
|
|
203
|
+
(b, c) = (b'\x12', "SYSTEM : MONITOR 1")
|
|
204
|
+
message = self.send_header + b + c.encode()
|
|
205
|
+
client.send(message)
|
|
206
|
+
# wait for acknowledge
|
|
207
|
+
if not self.wait_for_answer(client):
|
|
208
|
+
return
|
|
209
|
+
time.sleep(0.25)
|
|
210
|
+
|
|
211
|
+
def run_tcp_dat(self, cmd_queue: Queue, config_queue: Queue):
|
|
212
|
+
"""
|
|
213
|
+
Data TCP thread on port 6342. This handles receives all the streamed data from the device and decodes it.
|
|
214
|
+
"""
|
|
215
|
+
global data
|
|
216
|
+
global n_avg
|
|
217
|
+
types = np.dtype([
|
|
218
|
+
("signal_1", np.int32),
|
|
219
|
+
("signal_2", np.int32),
|
|
220
|
+
])
|
|
221
|
+
antenna_range = 1000.0
|
|
222
|
+
|
|
223
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
224
|
+
# Set the SO_REUSEADDR option to allow reuse of the port
|
|
225
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
226
|
+
|
|
227
|
+
# bind server to the address (only works when the address exists)
|
|
228
|
+
s.bind(self.data_server_address)
|
|
229
|
+
logging.info(f"[TCP DAT] Starting server at address {self.data_server_address}")
|
|
230
|
+
# wait for connections
|
|
231
|
+
s.listen()
|
|
232
|
+
# accept connection from device
|
|
233
|
+
client, addr = s.accept()
|
|
234
|
+
client.settimeout(2)
|
|
235
|
+
|
|
236
|
+
logging.info(f"[TCP DAT] Connected by client with address {addr}")
|
|
237
|
+
# now we are connected
|
|
238
|
+
while self.running.is_set():
|
|
239
|
+
if self.range_changed.is_set():
|
|
240
|
+
# range has changed and needs to be adjusted
|
|
241
|
+
# need to empty the read buffer
|
|
242
|
+
while self.running.is_set():
|
|
243
|
+
logging.debug(f"emptying buffer...")
|
|
244
|
+
try:
|
|
245
|
+
client.recv(32100)
|
|
246
|
+
except socket.timeout:
|
|
247
|
+
break
|
|
248
|
+
self.buffer_emptied.set()
|
|
249
|
+
self.range_changed.clear()
|
|
250
|
+
|
|
251
|
+
# data always comes in the shape of 4 datasets each as 16bit ints with length of
|
|
252
|
+
# (20 * self.range + 1)
|
|
253
|
+
# the header is 52 8 bit ints and since we read 8 bit ints we need to multiply the data by 2
|
|
254
|
+
if self.range_changed.is_set():
|
|
255
|
+
continue
|
|
256
|
+
if not self.acq_running.is_set():
|
|
257
|
+
# no data received
|
|
258
|
+
time.sleep(1.0)
|
|
259
|
+
continue
|
|
260
|
+
try:
|
|
261
|
+
raw_data = client.recv(2 * 4 * (20 * int(self.range) + 1) + self.full_data_header_len)
|
|
262
|
+
except socket.timeout:
|
|
263
|
+
continue
|
|
264
|
+
if not raw_data:
|
|
265
|
+
# no data received
|
|
266
|
+
time.sleep(1.0)
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
if raw_data[:self.data_header_len] != self.r_dat_header:
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
if len(raw_data) != 2 * 4 * (20 * int(self.range) + 1) + self.full_data_header_len:
|
|
273
|
+
skip_this = False
|
|
274
|
+
while self.running.is_set():
|
|
275
|
+
try:
|
|
276
|
+
to_append = client.recv(
|
|
277
|
+
2 * 4 * (20 * int(self.range) + 1) + self.full_data_header_len - len(raw_data))
|
|
278
|
+
raw_data += to_append
|
|
279
|
+
if len(raw_data) == 2 * 4 * (20 * int(self.range) + 1) + self.full_data_header_len:
|
|
280
|
+
break
|
|
281
|
+
if len(raw_data) == 2 * 4 * (20 * int(self.range) + 1) + self.full_data_header_len:
|
|
282
|
+
skip_this = True
|
|
283
|
+
break
|
|
284
|
+
except socket.timeout:
|
|
285
|
+
skip_this = True
|
|
286
|
+
break
|
|
287
|
+
if skip_this:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# TODO: the following code is not properly implemented yet, we need
|
|
291
|
+
# to check how we handle it, if the data comes not in the proper packet length
|
|
292
|
+
|
|
293
|
+
_data = raw_data[self.full_data_header_len:]
|
|
294
|
+
|
|
295
|
+
# check if header is at the beginning of the received payload
|
|
296
|
+
# if raw_data[:self.data_header_len] == self.r_dat_header:
|
|
297
|
+
# # remove the header
|
|
298
|
+
# _data = raw_data[self.data_header_len:]
|
|
299
|
+
# else:
|
|
300
|
+
# _data = raw_data[self.data_header_len:]
|
|
301
|
+
# # print("header not in the beginning")
|
|
302
|
+
# while self.running.is_set():
|
|
303
|
+
# logging.info("data does not have correct length...")
|
|
304
|
+
#
|
|
305
|
+
# # check if the data is of the correct length
|
|
306
|
+
# if len(_data) != 2 * 4 * (20 * self.range + 1):
|
|
307
|
+
# _data = _data + client.recv(2 * 4 * (20 * int(self.range) + 1) - len(_data))
|
|
308
|
+
# else:
|
|
309
|
+
# break
|
|
310
|
+
try:
|
|
311
|
+
# decode received payload to 32 bit ints
|
|
312
|
+
types = types.newbyteorder('>')
|
|
313
|
+
arr = np.frombuffer(_data, dtype=types)
|
|
314
|
+
|
|
315
|
+
if not config_queue.empty():
|
|
316
|
+
antenna_range = config_queue.get()
|
|
317
|
+
|
|
318
|
+
scale_factor = 20.0 * 1000.0 / antenna_range;
|
|
319
|
+
|
|
320
|
+
# TODO: make subtracting offset optional.
|
|
321
|
+
signal_1 = arr['signal_1'] / scale_factor / 2 ** 16 - arr['signal_1'][0] / scale_factor / 2 ** 16
|
|
322
|
+
signal_2 = arr['signal_2'] / scale_factor / 2 ** 16 - arr['signal_2'][0] / scale_factor / 2 ** 16
|
|
323
|
+
|
|
324
|
+
if self.avg_data.is_set():
|
|
325
|
+
if data.signal_1.shape == signal_1.shape:
|
|
326
|
+
data.signal_1 += signal_1
|
|
327
|
+
else:
|
|
328
|
+
data.signal_1 = signal_1
|
|
329
|
+
if data.signal_2.shape == signal_2.shape:
|
|
330
|
+
data.signal_2 += signal_2
|
|
331
|
+
n_avg += 1
|
|
332
|
+
else:
|
|
333
|
+
data.signal_1 = signal_1
|
|
334
|
+
data.signal_2 = signal_2
|
|
335
|
+
n_avg = 0
|
|
336
|
+
|
|
337
|
+
# do fft of signal 1
|
|
338
|
+
pulse = data.signal_1
|
|
339
|
+
f, a, arg = get_fft(data.time, pulse)
|
|
340
|
+
data.freq = f
|
|
341
|
+
data.fft_1_amp = a / np.max(a)
|
|
342
|
+
data.fft_1_phase = arg
|
|
343
|
+
|
|
344
|
+
# do fft of signal 2
|
|
345
|
+
pulse = data.signal_2
|
|
346
|
+
f, a, arg = get_fft(data.time, pulse)
|
|
347
|
+
data.fft_2_amp = a / np.max(a)
|
|
348
|
+
data.fft_2_phase = arg
|
|
349
|
+
|
|
350
|
+
# update avg countdown
|
|
351
|
+
if self.avg_countdown > 0:
|
|
352
|
+
self.avg_countdown -= 1
|
|
353
|
+
|
|
354
|
+
except Exception as e:
|
|
355
|
+
logging.error(e)
|
|
356
|
+
logging.error(f"{len(data.signal_1)=}")
|