diff --git a/src/arduino_device.py b/src/arduino_device.py index 02e81e9..4c744be 100644 --- a/src/arduino_device.py +++ b/src/arduino_device.py @@ -16,7 +16,7 @@ class ArduinoDevice(Arduino): ui_print("\nConnecting to Arduino...") # try to set up the arduino. Exceptions are handled by caller # search for connected arduino and connect by initializing arduino library class - Arduino.__init__(self, timeout=0.2) + Arduino.__init__(self, port="COM4", timeout=2) for pin in self.pins: self.pinMode(pin, "Output") self.digitalWrite(pin, "LOW") diff --git a/src/calibration.py b/src/calibration.py index 2d52b04..c18a67c 100644 --- a/src/calibration.py +++ b/src/calibration.py @@ -165,8 +165,10 @@ class CoilConstantCalibration(Thread): time.sleep(self.SETTLE_TIME) # Get coil constant - field_diff_mag = np.linalg.norm(g.MAGNETOMETER.field - ambient_field) - k_samples.append(field_diff_mag / c) + field = g.MAGNETOMETER.field + field_diff_mag = np.linalg.norm(field - ambient_field) + sign = 1 if field[i] - ambient_field[i] >= 0 else -1 + k_samples.append((field_diff_mag * sign) / c) # Save vector as principal coil direction if it is the last sample with the largest positive current if c_idx == currents.shape[0] - 1: @@ -177,6 +179,7 @@ class CoilConstantCalibration(Thread): k_deviations[i], _ = self.calculate_standard_deviation(k_samples) # Put device into an off and ready state + time.sleep(3) self.cage_dev.idle() angles = {'xy': self.angle_between(axis_field_directions[0], axis_field_directions[1]) * 180 / pi, diff --git a/src/globals.py b/src/globals.py index baab1cd..60f2053 100644 --- a/src/globals.py +++ b/src/globals.py @@ -48,4 +48,4 @@ default_psu_config = { # Configuration for socket interface SOCKET_PORT = 6677 -SOCKET_MAX_CONNECTIONS = 5 \ No newline at end of file +SOCKET_MAX_CONNECTIONS = 5 diff --git a/src/helmholtz_cage_device.py b/src/helmholtz_cage_device.py index 1f74706..68d1dec 100644 --- a/src/helmholtz_cage_device.py +++ b/src/helmholtz_cage_device.py @@ -1,3 +1,4 @@ +import time import traceback from threading import RLock, Thread, Event from tkinter import messagebox @@ -206,25 +207,21 @@ class HelmholtzCageDevice: command_buffer = HelmholtzCageDevice._copy_command(self.command) self.command = None # Processed commands are removed from "buffer" if command_buffer: - try: - # Unpack command into "action" and argument - command_string = command_buffer['command'] - command_arg = command_buffer['arg'] - # Check which command and delegate to responsible function - if command_string == "set_signed_currents": - self._set_signed_currents(command_arg) - elif command_string == "set_field_raw": - self._set_field_raw(command_arg) - elif command_string == "set_field_compensated": - self._set_field_compensated(command_arg) - elif command_string == 'idle': - self.idle() - else: - raise Exception("Command unknown!") - except Exception as e: - # No ui print, since this is really not something that should happen... (relevant for devs) - print("An error occurred while processing command: {}".format(self.command)) - traceback.print_exc() + # Unpack command into "action" and argument + command_string = command_buffer['command'] + command_arg = command_buffer['arg'] + # Check which command and delegate to responsible function + if command_string == "set_signed_currents": + self._set_signed_currents(command_arg) + elif command_string == "set_field_raw": + self._set_field_raw(command_arg) + elif command_string == "set_field_compensated": + self._set_field_compensated(command_arg) + elif command_string == 'idle': + self.idle() + else: + raise Exception("Command unknown!") + def _hw_poll_thread_method(self): """This method forms the main thread for hardware command execution.""" @@ -282,6 +279,7 @@ class HelmholtzCageDevice: axis.set_signed_current(current) except Exception as e: ui_print("Error {}: Unexpected error occured:\n{}".format(axis.name, e)) + traceback.print_exc() def get_psu_for_axis(self, axis_index): """Determine which channel of which psu is required""" @@ -443,7 +441,7 @@ class Axis: voltage_limit = min(max(1.3 * abs(safe_current) * self.resistance, 8), self.max_volts) # limit voltage # Set voltages and currents. Outputs should already be active from initializer. - self.psu.set_current(self.channel, safe_current) + self.psu.set_current(self.channel, abs(safe_current)) self.psu.set_voltage(self.channel, voltage_limit) @property diff --git a/src/ps2000b/PS2000B.py b/src/ps2000b/PS2000B.py index a1a3d7a..b0d008c 100644 --- a/src/ps2000b/PS2000B.py +++ b/src/ps2000b/PS2000B.py @@ -16,6 +16,7 @@ # [1] = "PS 2000B Programming Guide" from 2015-05-28 # [2] = "PS 2000B object list" # +import time import serial import struct @@ -388,7 +389,9 @@ class PS2000B: def set_current(self, value, channel): self.update_device_information(channel) + time.sleep(0.05) self.enable_remote_control(channel) + time.sleep(0.05) curr = int(round((value * 25600.0) / self.__device_information.nominal_current)) self.__send_device_data(Objects.SET_VALUE_CURRENT, curr, channel) diff --git a/src/psu_device.py b/src/psu_device.py index 68b4c1e..fe8fffb 100644 --- a/src/psu_device.py +++ b/src/psu_device.py @@ -1,4 +1,5 @@ # ABC is template used to create python abstract classes +import time from abc import ABC, abstractmethod import serial @@ -84,12 +85,15 @@ class PSUDevicePS2000B(PSUDevice): """Provides HW support for the Elektro-Automatik PS2000B power supply. This class is an adapter for the already existing PS2000B library, to have a consistent interface""" + MIN_DELAY = 0.05 + def __init__(self, com_port): super().__init__(com_port) """Can fail; Check for serial.SerialException""" self.dev = PS2000B.PS2000B(com_port) # dev_info is a class which contains hw specific constants, such as nominal voltage and current self.dev_info = self.dev.get_device_information() # Cache this result + time.sleep(self.MIN_DELAY) @staticmethod def valid_channels(): @@ -98,25 +102,32 @@ class PSUDevicePS2000B(PSUDevice): def enable_channel(self, channel_nr): self.dev.enable_output(channel_nr) + time.sleep(self.MIN_DELAY) def disable_channel(self, channel_nr): self.dev.enable_output(channel_nr) + time.sleep(self.MIN_DELAY) def set_current(self, channel_nr, current): self.dev.set_current(current, channel_nr) + time.sleep(self.MIN_DELAY) def set_voltage(self, channel_nr, voltage): self.dev.set_voltage(voltage, channel_nr) + time.sleep(self.MIN_DELAY) def poll_channel_state(self, channel_nr): self.dev.update_device_information(channel_nr) # update the information in the device object + time.sleep(self.MIN_DELAY) dev_status = self.dev.get_device_status_information(channel_nr) # get object with new status info # This is more efficient than the dev.get_voltage() call, since it does not require another update_device_info voltage = dev_status.actual_voltage_percent * self.dev_info.nominal_voltage # Extracted from PS2000B library voltage_setp = self.dev.get_voltage_setpoint(channel_nr) + time.sleep(self.MIN_DELAY) current = dev_status.actual_current_percent * self.dev_info.nominal_current current_setp = self.dev.get_current_setpoint(channel_nr) + time.sleep(self.MIN_DELAY) # Format should match the provided template in abstract PSUDevice class. return {'active': dev_status.output_active, 'remote_active': dev_status.remote_control_active, diff --git a/Helmholtz.ico b/tools/Helmholtz.ico similarity index 100% rename from Helmholtz.ico rename to tools/Helmholtz.ico diff --git a/auto-py-to-exe.exe b/tools/auto-py-to-exe.exe similarity index 100% rename from auto-py-to-exe.exe rename to tools/auto-py-to-exe.exe diff --git a/tools/fgm3d_adapter.py b/tools/fgm3d_adapter.py new file mode 100644 index 0000000..7a151b4 --- /dev/null +++ b/tools/fgm3d_adapter.py @@ -0,0 +1,35 @@ +import serial +from datetime import datetime +import socket + +# Lookup table for cage axis to magnetometer transformation +# First entry: Magnetometer y axis to cage x (reversed) +# axis_mapping = [[1, -1 ...] +axis_mapping = [[2, -1], [0, 1], [1, -1]] + +# Helmholtz control software tcp port +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect(("localhost", 6677)) +s.sendall("declare_api_version 2\n".encode()) + +# FGM3D software virtual serial port +ser = serial.Serial("COM11") + +line = b"" +ready = False +while True: + line += ser.read() + if line[-2:] == b"\r\n": + new_line = line[:-2].decode('ascii') + delta = datetime.now().timestamp()*1000 - int(new_line.split(';')[0])+(2*60*60*1000) + if delta < 500 and not ready: + ready = True + print("Program ready!") + if ready: + # Data is not valid otherwise + # Only use the x y and z values + tokens = [float(v) for v in new_line.split(";")[1:-1]] + axis_fields = [tokens[m[0]]*m[1] for m in axis_mapping] + axis_fields = ["{:.6e}".format(v) for v in axis_fields] + s.sendall(("magnetometer_field {} {} {}\n".format(*axis_fields)).encode()) + line = b""