Compare commits

..
5 Commits
Author SHA1 Message Date
Phil 2d4a5af40a sw: update UI 2026-09-07 18:55:42 +03:00
Phil 0526157bfc sw: change units 2026-09-07 18:54:01 +03:00
Phil 09e8ade74c sw: more information + auto-update feature 2026-09-07 18:48:20 +03:00
Phil 8d8b409807 sw: add load/save options, re-work adc/dac ratio setting 2026-09-07 17:41:23 +03:00
baulin.fa 8f342eebd7 Merge pull request 'dev/sw' (#14) from dev/sw into master
Reviewed-on: #14
2026-06-19 13:20:58 +03:00
2 changed files with 291 additions and 31 deletions
+133 -19
View File
@@ -2,6 +2,7 @@
import sys import sys
import math import math
import json
import socket import socket
import platform import platform
@@ -14,6 +15,19 @@ from PyQt6.QtCore import Qt
import pyqtgraph as pg import pyqtgraph as pg
from PyQt6.QtWidgets import QApplication, QMainWindow from PyQt6.QtWidgets import QApplication, QMainWindow
SETTINGS_LIST = [
"dac_dw",
"adc_dw",
"nmax",
"window_size",
"packet_size",
"adc_clk",
"dac_clk",
"accum_width",
"recv_port",
"send_port",
]
@dataclass @dataclass
class ReflectometerConfig: class ReflectometerConfig:
@@ -31,7 +45,9 @@ class ReflectometerConfig:
pulse_height: int pulse_height: int
pulse_num: int pulse_num: int
adc_dac_ratio: float = 0.52 # adc_dac_ratio: float = 0.52
adc_clk: float = 65
dac_clk: float = 125
socket_timeout_sec: float = 2.0 socket_timeout_sec: float = 2.0
@@ -46,6 +62,7 @@ class ReflectometerWorker(QObject):
self.config = config self.config = config
self._stop_requested = False self._stop_requested = False
self._sock = None self._sock = None
self.measure_timeout = 1.0
def stop(self): def stop(self):
self._stop_requested = True self._stop_requested = True
@@ -64,7 +81,7 @@ class ReflectometerWorker(QObject):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._sock.settimeout(self.config.socket_timeout_sec) self._sock.settimeout(self.measure_timeout)
self._sock.bind(("0.0.0.0", self.config.recv_port)) self._sock.bind(("0.0.0.0", self.config.recv_port))
dest = (self.config.ip, self.config.send_port) dest = (self.config.ip, self.config.send_port)
@@ -108,7 +125,8 @@ class ReflectometerWorker(QObject):
output += 0b10001000.to_bytes(1, "little") output += 0b10001000.to_bytes(1, "little")
pulse_period_adc = ( pulse_period_adc = (
int(self.config.pulse_period * self.config.adc_dac_ratio) int(self.config.pulse_period *
(self.config.adc_clk / self.config.dac_clk))
// self.config.window_size // self.config.window_size
) * self.config.window_size ) * self.config.window_size
@@ -126,7 +144,7 @@ class ReflectometerWorker(QObject):
def _recv_data(self) -> list[int]: def _recv_data(self) -> list[int]:
packet_count = math.ceil( packet_count = math.ceil(
( (
self.config.adc_dac_ratio (self.config.adc_clk / self.config.dac_clk)
* self.config.pulse_period * self.config.pulse_period
/ self.config.window_size / self.config.window_size
* self.config.data_width * self.config.data_width
@@ -135,7 +153,7 @@ class ReflectometerWorker(QObject):
) )
expected_length = math.ceil( expected_length = math.ceil(
self.config.adc_dac_ratio (self.config.adc_clk / self.config.dac_clk)
* self.config.pulse_period * self.config.pulse_period
/ self.config.window_size / self.config.window_size
) )
@@ -227,7 +245,6 @@ class MainWindow(QMainWindow):
self.nmax = 4096 self.nmax = 4096
self.packet_size = 1024 self.packet_size = 1024
self.window_size = 65 self.window_size = 65
self.adc_dac_ration = 0.52
self.accum_width = 32 self.accum_width = 32
# setup # setup
@@ -239,8 +256,6 @@ class MainWindow(QMainWindow):
self.data = [] self.data = []
self.adc_dac_ratio = 0.52
self.measurement_thread = None self.measurement_thread = None
self.measurement_worker = None self.measurement_worker = None
@@ -250,8 +265,58 @@ class MainWindow(QMainWindow):
self.button_start.clicked.connect(self.run_measurement) self.button_start.clicked.connect(self.run_measurement)
self.button_graph_autoscale.clicked.connect(self.reset_graph_autoscale) self.button_graph_autoscale.clicked.connect(self.reset_graph_autoscale)
# ping utils self.button_save.clicked.connect(self.save_config)
self.button_load.clicked.connect(self.load_config)
# timeout progress bar
self.measure_progress_timer = QTimer(self)
self.measure_progress_timer.setInterval(50)
self.measure_progress_timer.timeout.connect(
self.update_measure_progress)
self.measure_progress_elapsed_ms = 0
self.measure_progress_total_ms = 1
self.progress_measure.setRange(0, 100)
self.progress_measure.setValue(0)
def update_measure_progress(self):
self.measure_progress_elapsed_ms += self.measure_progress_timer.interval()
progress = int(
self.measure_progress_elapsed_ms
/ self.measure_progress_total_ms
* 100
)
self.progress_measure.setValue(min(progress, 100))
if progress >= 100:
self.measure_progress_timer.stop()
# load/save
def save_config(self):
conf = {}
for setting in SETTINGS_LIST:
conf[setting] = self.__getattribute__(setting)
with open(".settings.json", "w") as f:
f.write(json.dumps(conf))
self.label_save_status.setText("Настройки сохранены")
def load_config(self):
try:
with open(".settings.json", 'r') as f:
conf = json.loads(f.read())
except FileNotFoundError:
self.label_save_status.setText("Нет сохранненого .setting.json")
return
for setting in conf:
self.__getattribute__(f"box_{setting}").setValue(conf[setting])
self.label_save_status.setText("Настройки загружены")
# ping utils
def check_ping(self): def check_ping(self):
ip = self.line_ip.text().strip() ip = self.line_ip.text().strip()
@@ -280,14 +345,14 @@ class MainWindow(QMainWindow):
if system_name == "windows": if system_name == "windows":
program = "ping" program = "ping"
arguments = ["-n", "1", "-w", "2000", ip] arguments = ["-n", "1", "-w", "1000", ip]
else: else:
program = "ping" program = "ping"
arguments = ["-c", "1", "-W", "2", ip] arguments = ["-c", "1", "-W", "1", ip]
self.ping_process.start(program, arguments) self.ping_process.start(program, arguments)
# fallback # fallback
self.ping_timeout_timer.start(2000) self.ping_timeout_timer.start(1000)
def on_ping_finished(self, exit_code, exit_status): def on_ping_finished(self, exit_code, exit_status):
self.ping_timeout_timer.stop() self.ping_timeout_timer.stop()
@@ -371,6 +436,7 @@ class MainWindow(QMainWindow):
box.setValue(new_value) box.setValue(new_value)
setattr(self, name, new_value) setattr(self, name, new_value)
self.recalculate_times()
slider.valueChanged.connect(update_value) slider.valueChanged.connect(update_value)
box.valueChanged.connect(update_value) box.valueChanged.connect(update_value)
@@ -426,6 +492,22 @@ class MainWindow(QMainWindow):
) )
self.pulse_num = self.box_pulse_num.value() self.pulse_num = self.box_pulse_num.value()
def recalculate_times(self):
# пересчет показываемых метрик
LS = 200000 # ~km/s
period_us = (self.pulse_period / self.dac_clk)
self.label_meas_time.setText(
f"{round(period_us, 1)}мкс / {round(LS * period_us / 10e6, 1)}км")
self.label_duty.setText(
f"{round(self.pulse_width / self.pulse_period * 100, 4)}%")
self.label_voltage.setText(
f"{round((self.pulse_height - 8192) * 10 / 16384, 3)}V")
self.label_meas_total.setText(
f"{round(period_us * self.pulse_num / 10e6, 3)}с")
# settings # settings
def setup_global_settings(self): def setup_global_settings(self):
@@ -456,8 +538,13 @@ class MainWindow(QMainWindow):
) )
self._bind_spinbox_setting( self._bind_spinbox_setting(
name="adc_dac_ratio", name="adc_clk",
box=self.box_adc_dac_ratio, box=self.box_adc_clk,
)
self._bind_spinbox_setting(
name="dac_clk",
box=self.box_dac_clk,
) )
self._bind_spinbox_setting( self._bind_spinbox_setting(
@@ -554,7 +641,7 @@ class MainWindow(QMainWindow):
def setup_graph(self): def setup_graph(self):
self.graph_widget = pg.PlotWidget() self.graph_widget = pg.PlotWidget()
self.graph_widget.setLabel("left", "ADC value") self.graph_widget.setLabel("left", "ADC value")
self.graph_widget.setLabel("bottom", "Sample") self.graph_widget.setLabel("bottom", "Distance", units="km")
self.graph_widget.showGrid(x=True, y=True) self.graph_widget.showGrid(x=True, y=True)
self.graph_curve = self.graph_widget.plot( self.graph_curve = self.graph_widget.plot(
@@ -596,10 +683,21 @@ class MainWindow(QMainWindow):
config = self.build_reflectometer_config() config = self.build_reflectometer_config()
self.data = [] self.data = []
self.graph_curve.setData([])
# self.graph_curve.setData([])
self.measurement_thread = QThread(self) self.measurement_thread = QThread(self)
self.measurement_worker = ReflectometerWorker(config) self.measurement_worker = ReflectometerWorker(config)
period_us = (self.pulse_period / self.dac_clk)
self.measurement_worker.measure_timeout = period_us * self.pulse_num / 10e6 + 0.2
self.measure_progress_total_ms = int(
self.measurement_worker.measure_timeout * 1000 - 199
)
self.measure_progress_elapsed_ms = 0
self.progress_measure.setValue(0)
self.measure_progress_timer.start()
self.measurement_worker.moveToThread(self.measurement_thread) self.measurement_worker.moveToThread(self.measurement_thread)
@@ -642,7 +740,8 @@ class MainWindow(QMainWindow):
pulse_height=self.pulse_height, pulse_height=self.pulse_height,
pulse_num=self.pulse_num, pulse_num=self.pulse_num,
adc_dac_ratio=self.adc_dac_ratio, adc_clk=self.adc_clk,
dac_clk=self.dac_clk
) )
def on_data_received(self, data: list[int]): def on_data_received(self, data: list[int]):
@@ -664,10 +763,14 @@ class MainWindow(QMainWindow):
def on_measurement_error(self, message: str): def on_measurement_error(self, message: str):
self.set_measurement_status(f"Ошибка: {message}") self.set_measurement_status(f"Ошибка: {message}")
if self.checkbox_autorestart.isChecked():
self.run_measurement()
def on_measurement_finished(self): def on_measurement_finished(self):
self.measurement_worker = None self.measurement_worker = None
self.measurement_thread = None self.measurement_thread = None
if self.checkbox_autorestart.isChecked():
self.run_measurement()
def stop_measurement(self): def stop_measurement(self):
if self.measurement_worker is not None: if self.measurement_worker is not None:
@@ -681,7 +784,18 @@ class MainWindow(QMainWindow):
self.graph_curve.setData([]) self.graph_curve.setData([])
return return
x = list(range(len(self.data))) LS = 200000 # km/s
period_us = self.pulse_period / self.dac_clk
max_distance_km = LS * period_us / 10e6
sample_count = len(self.data)
x = [
i * max_distance_km / sample_count
for i in range(sample_count)
]
self.graph_curve.setData(x, self.data) self.graph_curve.setData(x, self.data)
def update_reference_graph(self): def update_reference_graph(self):
@@ -711,7 +825,7 @@ class MainWindow(QMainWindow):
reference = [0] * length reference = [0] * length
actual_pulse_width = round( actual_pulse_width = round(
(self.pulse_width * self.adc_dac_ratio) / self.window_size) (self.pulse_width * self.adc_clk / self.dac_clk) / self.window_size)
reference[0:actual_pulse_width] = [ reference[0:actual_pulse_width] = [
(self.pulse_height / 2 ** (self.dac_dw - self.adc_dw)) - 2 ** (self.adc_dw - 1), ] * (actual_pulse_width - 1) (self.pulse_height / 2 ** (self.dac_dw - self.adc_dw)) - 2 ** (self.adc_dw - 1), ] * (actual_pulse_width - 1)
+158 -12
View File
@@ -7,11 +7,11 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1023</width> <width>1023</width>
<height>708</height> <height>844</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Reflectometer PREMIUM</string> <string>Reflectometer PRO MAX</string>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="4,2"> <layout class="QHBoxLayout" name="horizontalLayout" stretch="4,2">
@@ -39,9 +39,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>-32</y>
<width>294</width> <width>294</width>
<height>621</height> <height>762</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@@ -118,21 +118,37 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDoubleSpinBox" name="box_adc_dac_ratio"> <widget class="QSpinBox" name="box_adc_clk">
<property name="suffix">
<string> MHz</string>
</property>
<property name="prefix"> <property name="prefix">
<string>ADC:DAC clk ratio: </string> <string>ADC CLK: </string>
</property> </property>
<property name="minimum"> <property name="minimum">
<double>0.200000000000000</double> <number>25</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<double>3.000000000000000</double> <number>350</number>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property> </property>
<property name="value"> <property name="value">
<double>0.520000000000000</double> <number>65</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="box_dac_clk">
<property name="suffix">
<string> MHz</string>
</property>
<property name="prefix">
<string>DAC CLK: </string>
</property>
<property name="minimum">
<number>25</number>
</property>
<property name="maximum">
<number>350</number>
</property> </property>
</widget> </widget>
</item> </item>
@@ -292,6 +308,50 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Конфигурация</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_save_status">
<property name="text">
<string>Можно сохранить или загрузить</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QPushButton" name="button_save">
<property name="text">
<string>Сохранить</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_load">
<property name="text">
<string>Загрузить</string>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
@@ -353,6 +413,24 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="label_15">
<property name="text">
<string>Время измерения:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_meas_time">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,1,2"> <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,1,2">
<item> <item>
@@ -374,6 +452,24 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>Скважность:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_duty">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,1,2"> <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,1,2">
<item> <item>
@@ -395,6 +491,24 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_17">
<property name="text">
<string>Напряжение:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_voltage">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,1,2"> <layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,1,2">
<item> <item>
@@ -423,6 +537,24 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QLabel" name="label_14">
<property name="text">
<string>Суммарное время:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_meas_total">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QPushButton" name="button_start"> <widget class="QPushButton" name="button_start">
<property name="text"> <property name="text">
@@ -453,6 +585,20 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QProgressBar" name="progress_measure">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_autorestart">
<property name="text">
<string>Авто-рестарт</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="checkbox_draw_reference"> <widget class="QCheckBox" name="checkbox_draw_reference">
<property name="text"> <property name="text">