From bcadc3f273e5a526b7ba00a065308e7c8c682371 Mon Sep 17 00:00:00 2001 From: Leon Teichroeb Date: Mon, 23 Aug 2021 01:02:53 +0200 Subject: [PATCH] Added user interface elements for magnetometer calibration --- src/helmholtz_cage_device.py | 11 +++--- src/socket_control.py | 49 +++++++++++++------------- src/user_interface.py | 66 +++++++++++++++++++++++++++++++++++- 3 files changed, 98 insertions(+), 28 deletions(-) diff --git a/src/helmholtz_cage_device.py b/src/helmholtz_cage_device.py index 68d1dec..2f894b9 100644 --- a/src/helmholtz_cage_device.py +++ b/src/helmholtz_cage_device.py @@ -81,6 +81,9 @@ class HelmholtzCageDevice: # The axes talks to the HW objects (Arduino, PSU) referenced in this object self.axes.append(Axis(i, self)) + # Zero and activate channels. This is a sort of "armed" state so that we can send commands later + self.idle() + # --- HW COMMUNICATION THREAD --- self._cmd_exec_thread = Thread(target=self._cmd_exec_thread_method) self._cmd_exec_thread.start() @@ -92,6 +95,7 @@ class HelmholtzCageDevice: def reconnect_hardware(self): self.shutdown() self.connect_hardware() + self.idle() # TODO: Move to proxy def connect_hardware(self): @@ -133,9 +137,6 @@ class HelmholtzCageDevice: self.psu2 = None ui_print("Error creating PSU device:\n{}".format(e)) - # Zero and activate channels. This is a sort of "armed" state so that we can send commands later - self.idle() - def idle(self): """ Zero and activate channels """ if self.psu1 is not None: @@ -144,6 +145,9 @@ class HelmholtzCageDevice: self.psu2.idle() if self.arduino is not None: self.arduino.idle() + # Since these actions are not handled by the axes objects, also make sure to update their target field status + for axis in self.axes: + axis.target_current = 0 def request_proxy(self): """Returns a new HelmholtzCageProxy or None, depending on if access is available""" @@ -222,7 +226,6 @@ class HelmholtzCageDevice: else: raise Exception("Command unknown!") - def _hw_poll_thread_method(self): """This method forms the main thread for hardware command execution.""" while True: diff --git a/src/socket_control.py b/src/socket_control.py index 0c501e3..d0a02c4 100644 --- a/src/socket_control.py +++ b/src/socket_control.py @@ -74,29 +74,32 @@ class ClientConnectionThread(Thread): def run(self): msg = '' while True: - raw_msg = self.client_socket.recv(2048).decode() - # Check for end of stream - if raw_msg == "": - self.client_socket.close() - if self._cage_dev: - self._cage_dev.close() - g.MAGNETOMETER.connected = False - return - # Process message - for char in raw_msg: - if char == '\n': - msg = msg.rstrip() # Some systems will try to send \r characters... looking at you windows O_O - try: - response = self.handle_msg(msg) - except Exception as e: - ui_print("An error occurred while processing a client message") - ui_print("Msg: {}".format(msg)) - ui_print(e) - response = "err" - self.client_socket.sendall((response + '\n').encode('utf-8')) - msg = '' - else: - msg += char + try: + raw_msg = self.client_socket.recv(2048).decode() + # Check for end of stream + if raw_msg == "": + self.client_socket.close() + if self._cage_dev: + self._cage_dev.close() + g.MAGNETOMETER.connected = False + return + # Process message + for char in raw_msg: + if char == '\n': + msg = msg.rstrip() # Some systems will try to send \r characters... looking at you windows O_O + try: + response = self.handle_msg(msg) + except Exception as e: + ui_print("An error occurred while processing a client message") + ui_print("Msg: {}".format(msg)) + ui_print(e) + response = "err" + self.client_socket.sendall((response + '\n').encode('utf-8')) + msg = '' + else: + msg += char + except ConnectionResetError: + ui_print("A connection was closed by the client.") def handle_msg(self, message): """ Executes command logic and returns string response (for client). """ diff --git a/src/user_interface.py b/src/user_interface.py index 22396e5..689f750 100644 --- a/src/user_interface.py +++ b/src/user_interface.py @@ -17,6 +17,7 @@ import os from os.path import exists import threading from datetime import datetime +from math import pi # import other project files: import src.globals as g @@ -822,6 +823,11 @@ class CalibrateMagnetometer(Frame): # Calibration parameters self.calibration_points_var = IntVar(value=8) self.calibration_interval_var = DoubleVar(value=5) + # Calibration results + self.sensitivity_result_vars = [StringVar(), StringVar(), StringVar()] + self.angle_to_plane_result_vars = [StringVar(), StringVar(), StringVar()] + self.angle_in_plane_result_vars = [StringVar(), StringVar(), StringVar()] + self.residual_result_vars = [StringVar(), StringVar(), StringVar()] # UI Elements row_counter = 0 @@ -888,6 +894,60 @@ class CalibrateMagnetometer(Frame): calibration_procedure_progress.grid(row=0, column=1, padx=10, pady=10, sticky="we") row_counter += 1 + # RIGHT COLUMN + # Magnetometer calibration results + row_counter = 0 + calibration_results_frame = LabelFrame(self.right_column, text="Ambient Field Results") + calibration_results_frame.grid(row=row_counter, column=1, padx=(100, 0), pady=20, sticky="nw") + for i, label in enumerate(['X', 'Y', 'Z']): + axis_label = Label(calibration_results_frame, text=label) + axis_label.grid(row=0, column=i + 1, padx=5, pady=5, sticky="nw") + # Axis sensitivities + sensitivity_results_label = Label(calibration_results_frame, text="Sensitivity:") + sensitivity_results_label.grid(row=1, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + axis_data = Entry(calibration_results_frame, + textvariable=self.sensitivity_result_vars[i], + width=15, + state='readonly') + axis_data.grid(row=1, column=i + 1, padx=5, pady=5, sticky="nw") + sensitivity_results_unit = Label(calibration_results_frame, text="-") + sensitivity_results_unit.grid(row=1, column=4, padx=5, pady=5, sticky="nw") + # Angle to XY coil plane + angle_to_plane_label = Label(calibration_results_frame, text="Angle to XY plane:") + angle_to_plane_label.grid(row=2, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + axis_data = Entry(calibration_results_frame, + textvariable=self.angle_to_plane_result_vars[i], + width=15, + state='readonly') + axis_data.grid(row=2, column=i + 1, padx=5, pady=5, sticky="nw") + angle_to_plane_unit = Label(calibration_results_frame, text="°") + angle_to_plane_unit.grid(row=2, column=4, padx=5, pady=5, sticky="nw") + # Angle in XY coil plane + angle_in_plane_label = Label(calibration_results_frame, text="Angle in XY plane:") + angle_in_plane_label.grid(row=3, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + axis_data = Entry(calibration_results_frame, + textvariable=self.angle_in_plane_result_vars[i], + width=15, + state='readonly') + axis_data.grid(row=3, column=i + 1, padx=5, pady=5, sticky="nw") + angle_in_plane_unit = Label(calibration_results_frame, text="°") + angle_in_plane_unit.grid(row=3, column=4, padx=5, pady=5, sticky="nw") + # Residual in system of equations + residual_label = Label(calibration_results_frame, text="Residual:") + residual_label.grid(row=4, column=0, padx=5, pady=5, sticky="nw") + for i in range(3): + axis_data = Entry(calibration_results_frame, + textvariable=self.residual_result_vars[i], + width=15, + state='readonly') + axis_data.grid(row=4, column=i + 1, padx=5, pady=5, sticky="nw") + residual_unit = Label(calibration_results_frame, text="\u03BCT") + residual_unit.grid(row=4, column=4, padx=5, pady=5, sticky="nw") + row_counter += 1 + # This starts an endless polling loop self.update_view() @@ -940,7 +1000,11 @@ class CalibrateMagnetometer(Frame): self.start_calibration_button.configure(text="Running...", state=DISABLED) def display_calibration_results(self, results): - pass + for i in range(3): + self.sensitivity_result_vars[i].set("{:.3f}".format(results[i]['sensitivity'])) + self.angle_to_plane_result_vars[i].set("{:.3f}".format(results[i]['alpha'] * 180/pi)) + self.angle_in_plane_result_vars[i].set("{:.3f}".format(results[i]['beta'] * 180/pi)) + self.residual_result_vars[i].set("{:.3f}".format(results[i]['residual'] * 1e6)) def start_calibration_procedure(self): try: