client-gui.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import numpy as np
  2. from matplotlib.figure import Figure
  3. import socket
  4. import msgpack
  5. from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QMessageBox
  6. from PySide6.QtWidgets import QDialog, QFormLayout, QLineEdit, QLabel, QComboBox, QSpinBox, QToolButton, QFileDialog
  7. from PySide6.QtCore import QTimer
  8. from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
  9. from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
  10. from PySide6.QtWidgets import QSizePolicy
  11. import json
  12. class MplCanvas(FigureCanvas):
  13. def __init__(self, parent=None, width=5, height=4, dpi=100):
  14. self.fig = Figure(figsize=(width, height), dpi=dpi, facecolor='grey')
  15. super().__init__(self.fig)
  16. self.setParent(parent)
  17. self.axes = self.fig.add_subplot(111)
  18. # Построим простой график
  19. self.axes.plot([0, 1, 2, 3], [10, 1, 20, 3])
  20. self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
  21. class SDRClient(QMainWindow):
  22. def __init__(self):
  23. super().__init__()
  24. # Параметры подключения к серверу
  25. self.server_ip = '127.0.0.1'
  26. self.server_port = 5003
  27. self.sock = None
  28. # Параметры для отрисовки
  29. self.channel_data = {
  30. 'time': np.array([]),
  31. 'channel_g': np.array([]),
  32. 'channel_rf': np.array([]),
  33. 'channel_adc_ttl': np.array([]),
  34. 'channel_rf_ttl': np.array([])
  35. }
  36. self.wavetypes = ["default"]
  37. # Настройка окна
  38. self.setWindowTitle("SDR Client")
  39. self.setGeometry(100, 100, 1280, 720)
  40. self.central_widget = QWidget()
  41. self.setCentralWidget(self.central_widget)
  42. self.layout = QHBoxLayout()
  43. self.plot_layout = QVBoxLayout()
  44. self.menu_layout = QVBoxLayout()
  45. self.layout.addLayout(self.menu_layout)
  46. self.layout.addLayout(self.plot_layout)
  47. self.central_widget.setLayout(self.layout)
  48. self.command_layout = QHBoxLayout()
  49. self.plot_layout.addLayout(self.command_layout)
  50. self.add_event_button = QPushButton("Add Event")
  51. self.command_layout.addWidget(self.add_event_button)
  52. self.add_rfwave_button = QPushButton("Add RF Wave")
  53. self.command_layout.addWidget(self.add_rfwave_button)
  54. self.run_event_list_button = QPushButton("Run Event List")
  55. self.command_layout.addWidget(self.run_event_list_button)
  56. self.clear_event_list_button = QPushButton("Clear Event List")
  57. self.command_layout.addWidget(self.clear_event_list_button)
  58. self.plot_canvas = MplCanvas(self, width=5, height=4, dpi=100)
  59. #self.plot_canvas.setSizePolicy(self.plot_widget.sizePolicy())
  60. self.plot_layout.addWidget(self.plot_canvas, stretch=1)
  61. toolbar = NavigationToolbar(self.plot_canvas, self)
  62. self.plot_layout.addWidget(toolbar, stretch=1)
  63. self.connect_button = QPushButton("Connect to SDR")
  64. self.connect_button.setMinimumSize(200, 50)
  65. self.disconnect_button = QPushButton("Disconnect")
  66. self.disconnect_button.setMinimumSize(200, 50)
  67. self.settings_button = QPushButton("Settings")
  68. self.settings_button.setMinimumSize(200, 50)
  69. self.recv_sim_data_button = QPushButton("Receive Sim Data")
  70. self.recv_sim_data_button.setMinimumSize(200, 50)
  71. self.menu_layout.addWidget(self.connect_button)
  72. self.menu_layout.addWidget(self.disconnect_button)
  73. self.menu_layout.addWidget(self.settings_button)
  74. self.menu_layout.addWidget(self.recv_sim_data_button)
  75. self.connect_button.clicked.connect(self.connect_to_server)
  76. self.disconnect_button.clicked.connect(self.disconnect_from_server)
  77. self.add_event_button.clicked.connect(self.add_event_dialog)
  78. self.add_rfwave_button.clicked.connect(self.add_wavetype_dialog)
  79. def connect_to_server(self):
  80. try:
  81. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  82. self.sock.connect((self.server_ip, self.server_port))
  83. print(f"Connected to SDR server at {self.server_ip}:{self.server_port}")
  84. except Exception as e:
  85. print(f"Failed to connect to server: {e}")
  86. QMessageBox.critical(self, "Connection Error", f"Failed to connect to server: {e}")
  87. self.sock = None
  88. def disconnect_from_server(self):
  89. if self.sock:
  90. self.sock.close()
  91. self.sock = None
  92. QMessageBox.information(self, "Disconnected", "Successfully disconnected from SDR server")
  93. print("Disconnected from SDR server")
  94. def add_event_dialog(self):
  95. #if not self.sock:
  96. # QMessageBox.warning(self, "Not Connected", "Please connect to the SDR server first")
  97. # return False
  98. add_event_dialog = QDialog(self)
  99. add_event_dialog.setWindowTitle("Add Event")
  100. layout = QFormLayout()
  101. labels = ["Start Time (ticks)", "Duration (ticks)", "Waveform Type", "time_front_rf_ttl (ticks)", "time_back_rf_ttl (ticks)", "time_front_adc_ttl (ticks)", "time_back_adc_ttl (ticks)", "time_start_rf (ticks)", "time_end_rf (ticks)", "rf_freq (Hz)"]
  102. self.event_inputs = {}
  103. for label in labels:
  104. if label == "Waveform Type":
  105. combo = QComboBox()
  106. combo.addItems(self.wavetypes)
  107. layout.addRow(QLabel(label), combo)
  108. self.event_inputs[label] = combo
  109. else:
  110. line_edit = QLineEdit()
  111. layout.addRow(QLabel(label), line_edit)
  112. self.event_inputs[label] = line_edit
  113. submit_button = QPushButton("Submit")
  114. layout.addWidget(submit_button)
  115. load_event_list_button = QPushButton("Load Event List")
  116. layout.addWidget(load_event_list_button)
  117. submit_button.clicked.connect(self.submit_event)
  118. load_event_list_button.clicked.connect(self.load_event_list)
  119. add_event_dialog.setLayout(layout)
  120. add_event_dialog.exec()
  121. def submit_event(self):
  122. try:
  123. event_data = {
  124. "time": int(self.event_inputs["Start Time (ticks)"].text()),
  125. "duration": int(self.event_inputs["Duration (ticks)"].text()),
  126. "wave": self.event_inputs["Waveform Type"].currentText(),
  127. "time_front_rf_trigger": int(self.event_inputs["time_front_rf_ttl (ticks)"].text()),
  128. "time_back_rf_trigger": int(self.event_inputs["time_back_rf_ttl (ticks)"].text()),
  129. "time_front_adc_trigger": int(self.event_inputs["time_front_adc_ttl (ticks)"].text()),
  130. "time_back_adc_trigger": int(self.event_inputs["time_back_adc_ttl (ticks)"].text()),
  131. "rf_start": int(self.event_inputs["time_start_rf (ticks)"].text()),
  132. "rf_end": int(self.event_inputs["time_end_rf (ticks)"].text()),
  133. "carrier_freq": float(self.event_inputs["rf_freq (Hz)"].text())
  134. }
  135. msg = msgpack.packb({"command": 0x10, "data": event_data})
  136. self.sock.sendall(msg)
  137. print("Event added successfully")
  138. QMessageBox.information(self, "Success", "Event added successfully")
  139. self.add_event_dialog.close()
  140. except Exception as e:
  141. print(f"Failed to add event: {e}")
  142. QMessageBox.critical(self, "Error", f"Failed to add event: {e}")
  143. def load_event_list(self):
  144. file_dialog = QFileDialog(self)
  145. file_dialog.setNameFilter("JSON Files (*.json)")
  146. if file_dialog.exec():
  147. selected_file = file_dialog.selectedFiles()[0]
  148. try:
  149. with open(selected_file, 'r') as f:
  150. event_list = json.load(f)
  151. for event_data in event_list:
  152. msg = {
  153. "magic": 0xAA,
  154. "cmd": 0x10,
  155. "time": event_data["time"],
  156. "duration": event_data["duration"],
  157. "wave": event_data["wave"],
  158. "time_front_rf_trigger": event_data["time_front_rf_trigger"],
  159. "time_back_rf_trigger": event_data["time_back_rf_trigger"],
  160. "time_front_adc_trigger": event_data["time_front_adc_trigger"],
  161. "time_back_adc_trigger": event_data["time_back_adc_trigger"],
  162. "rf_start": event_data["rf_start"],
  163. "rf_end": event_data["rf_end"],
  164. "carrier_freq": event_data["carrier_freq"]
  165. }
  166. packed_msg = msgpack.packb(msg, use_bin_type=True)
  167. self.sock.sendall(packed_msg)
  168. resp = self.sock.recv(1024)
  169. if not resp:
  170. raise Exception("No response from server")
  171. unpacked_resp = msgpack.unpackb(resp, raw=False)
  172. if unpacked_resp["cmd"] != 0xFE and unpacked_resp["code"] != 0x00000010:
  173. raise Exception(f"Server error: {unpacked_resp['message']}")
  174. print("Event list loaded successfully")
  175. QMessageBox.information(self, "Success", "Event list loaded successfully")
  176. self.add_event_dialog.close()
  177. except Exception as e:
  178. print(f"Failed to load event list: {e}")
  179. QMessageBox.critical(self, "Error", f"Failed to load event list: {e}")
  180. def add_wavetype_dialog(self):
  181. add_wave_dialog = QDialog(self)
  182. add_wave_dialog.setWindowTitle("Add Waveform Type")
  183. layout = QFormLayout()
  184. name_input = QLineEdit()
  185. layout.addRow(QLabel("Waveform Name"), name_input)
  186. self.file_input = QLineEdit()
  187. file_dialog_button = QPushButton("Browse")
  188. file_dialog_button.clicked.connect(lambda: self.open_wave_file_dialog(self.file_input))
  189. layout.addRow(QLabel("Waveform File"), self.file_input)
  190. layout.addWidget(file_dialog_button)
  191. submit_button = QPushButton("Submit")
  192. layout.addWidget(submit_button)
  193. submit_button.clicked.connect(lambda: self.submit_wavetype(name_input.text(), self.file_input.text(), add_wave_dialog))
  194. add_wave_dialog.setLayout(layout)
  195. add_wave_dialog.exec()
  196. def open_wave_file_dialog(self, file_input):
  197. file_dialog = QFileDialog(self)
  198. file_dialog.setNameFilter("All Files (*);;Binary Files (*.bin)")
  199. if file_dialog.exec():
  200. selected_file = file_dialog.selectedFiles()[0]
  201. file_input.setText(selected_file)
  202. def submit_wavetype(self, name, file_path, dialog):
  203. if not name:
  204. QMessageBox.warning(self, "Input Error", "Please enter a waveform name")
  205. return
  206. if name in self.wavetypes:
  207. QMessageBox.warning(self, "Input Error", "Waveform name already exists")
  208. return
  209. header_msg = {
  210. "magic": 0xAA,
  211. "cmd": 0x20,
  212. "wavetype": name,
  213. "total_length": 0, # Placeholder, will be updated after loading the waveform data
  214. "total_packets": 0, # Placeholder, will be updated after loading the waveform data
  215. }
  216. with open(file_path, 'rb') as f:
  217. # Load rows point from .bin file (first 2 bytes is Q-point, next 2 bytes is I-point)
  218. total_wavedata_q = np.fromfile(f, dtype=np.int16, count=-1, offset=0)
  219. total_wavedata_i = np.fromfile(f, dtype=np.int16, count=-1, offset=2)
  220. if len(total_wavedata_q) != len(total_wavedata_i):
  221. QMessageBox.warning(self, "Input Error", "In-phase and quadrature wave data must have the same length")
  222. return
  223. packet_length = 512 # Define packet length for sending waveform data
  224. header_msg["total_length"] = len(total_wavedata_q)
  225. header_msg["total_packets"] = len(total_wavedata_q) // packet_length + (1 if len(total_wavedata_q) % packet_length > 0 else 0)
  226. try:
  227. packed_header = msgpack.packb(header_msg, use_bin_type=True)
  228. self.sock.sendall(packed_header)
  229. raw_resp = self.sock.recv(1024)
  230. if not raw_resp:
  231. raise Exception("No response from server")
  232. resp = msgpack.unpackb(raw_resp, raw=False)
  233. if resp["cmd"] != 0xFE or resp["code"] != 0x00000001:
  234. raise Exception(f"Server error: {resp['message']}")
  235. except Exception as e:
  236. print(f"Failed to send waveform header: {e}")
  237. QMessageBox.critical(self, "Error", f"Failed to send waveform header: {e}")
  238. return
  239. try:
  240. for i in range(header_msg["total_packets"]):
  241. packet_length = min(512, len(total_wavedata_q) - i * 512)
  242. start_idx = i * 512
  243. end_idx = min((i + 1) * 512, len(total_wavedata_q))
  244. packet_data = {
  245. "magic": 0xAA,
  246. "cmd": 0x2C,
  247. "packet_index": i,
  248. "packet_length": packet_length,
  249. "wavedata_q": total_wavedata_q[start_idx:end_idx],
  250. "wavedata_i": total_wavedata_i[start_idx:end_idx]
  251. }
  252. packed_packet = msgpack.packb(packet_data, use_bin_type=True)
  253. self.sock.sendall(packed_packet)
  254. raw_resp = self.sock.recv(1024)
  255. if not raw_resp:
  256. raise Exception("No response from server")
  257. resp = msgpack.unpackb(raw_resp, raw=False)
  258. if resp["cmd"] != 0xFE or resp["code"] != 0x00000002:
  259. raise Exception(f"Server error: {resp['message']}")
  260. self.sock.sendall(b"ACK") # Send ACK to server to indicate ready for next packet
  261. raw_resp = self.sock.recv(1024)
  262. if not raw_resp:
  263. raise Exception("No response from server after sending all packets")
  264. resp = msgpack.unpackb(raw_resp, raw=False)
  265. if resp["cmd"] != 0xFE or resp["code"] != 0x00000003:
  266. raise Exception(f"Server error after sending all packets: {resp['message']}")
  267. except Exception as e:
  268. print(f"Failed to send waveform data: {e}")
  269. QMessageBox.critical(self, "Error", f"Failed to send waveform data: {e}")
  270. return
  271. self.wavetypes.append(name)
  272. print(f"Added new waveform type: {name}")
  273. QMessageBox.information(self, "Success", f"Added new waveform type: {name}")
  274. dialog.close()
  275. app = QApplication([])
  276. client = SDRClient()
  277. client.show()
  278. #plt.ion() # Enable interactive mode for plotting
  279. #plt.show()
  280. app.exec()