from typing import List, Tuple
import logging
import time
# Import essential modules
import serial
# Set up logger
logger = logging.getLogger(__name__)
[docs]class ChopperController:
"""
Initializes a serial connection with the chopper controller.
For completeness, this class contains all commands which are
provided in the MC2000B Manual.
Args:
port (str): COM port to which the controller is connected.
Please check in the device manager of your operating system.
name (str, optional): Name / Identifier to give to this
controller. This is relevant for log statements, especially when
there is more than one motor in the setup. Defaults to
"Chopper".
Attributes:
name (str, optional): Name / Identifier to give to this
controller. This is relevant for log statements, especially when
there is more than one motor in the setup. Defaults to
"Chopper".
ser (serial.Serial): Pyserial object which initiates the
communication with the Counter.
blade_type (str): Identifier of the blade that the chopper
thinks is installed
frequency (str): Frequency of chopper in Hz
harmonic_divider (str): Divides the incoming frequency by the
which is set via the harmonic divider.
phase (str): Chopper phase in degrees.
References:
| MC2000B-Manual.pdf
| https://github.com/AKuederle/thorlabs_chopper_interface
"""
def __init__(self, port: str, name: str = "Chopper"):
# Assigning attributes
self.name = name
# Connecting the controller with pyserial
self.ser = serial.Serial(
port,
115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1,
)
logger.info('Port is open."{}" is ready.'.format(self.name))
self.get_blade_type()
logger.info("Blade type is {} for {}".format(self.blade_type, self.name))
# self.get_frequency()
# logger.info("Frequency is {} Hz for {}".format(self.frequency, self.name))
self.get_harmonic_divider()
logger.info(
"Harmonic divider is {} for {}".format(self.harmonic_divider, self.name)
)
self.get_phase()
logger.info("Phase is {}° for {}".format(self.phase, self.name))
self.get_enable()
if self.enabled == "1":
logger.info("{} is running.".format(self.name))
else:
logger.info("{} is not running.".format(self.name))
[docs] def read(self):
"""
Reads out the controllers answer to a sent command.
The byte object is not transformed into a string because
problems due to invisible characters arise. It is easy to
understands what the controller returns by seeing the command
and the value.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
readout = self.ser.readline()
return readout
[docs] def parse_data(self, command: str, readout: bytes):
"""
Extracts the information from the bytes that is returned from
the chopper.
Args:
command (str): Command that was sent to controller.
readout (bytes): Bytecode that controller returned after
sending command.
Returns:
str: Relevant information
"""
return readout.decode("utf-8").replace(command + "\r", "").replace("\r> ", "")
[docs] def send_command(self, input_command: str):
"""
Creates necessary ascii format of the command which will be send
to the controller
Args:
input_command (str): Command which should be send to the
controller
Returns:
str: Command which is send to card. In ascii format.
"""
# .encode is used to transform command into byte
ascii_command = (str(input_command) + "\r").encode()
self.ser.write(ascii_command)
[docs] def get_commands(self):
"""
List the available commands.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "?"
self.send_command(command)
return self.read()
[docs] def get_identification(self):
"""
Returns the model number and firmware version.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "id?"
self.send_command(command)
self.read()
[docs] def set_frequency(self, value: int):
"""
Set the desired internal reference frequency.
Args:
value (int): Frequency in Herz (Hz).
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "freq={}".format(value)
self.send_command(command)
output = self.read()
self.get_frequency()
return output
[docs] def get_frequency(self):
"""
Returns the internal reference frequency.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "freq?"
self.send_command(command)
self.frequency = self.parse_data(command, self.read())
return self.frequency
[docs] def get_ref_out_frequency(self):
"""
Returns the reference output frequency.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "refoutfreq?"
self.send_command(command)
return self.read()
[docs] def set_blade_type(self, value: int):
"""
Set the blade type (value is number indicated in section 8.2.
For explanation look in section 6.4).
* MC1F2 = 0
* MC1F10 = 1
* MC1F15 = 2
* MC1F30 = 3
* MC1F60 = 4
* MC1F100 = 5
* MC1F10HP = 6
* MC1F2P10 = 7
* MC1F6P10 = 8
* MC1F10A = 9
* MC2F330 = 10
* MC2F47 = 11
* MC2F57B = 12
* MC2F860 = 13
* MC2F5360 = 14
Args:
value (int): Number which defines the blade type.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
if value > 14 or value < 0:
raise ValueError(
"Incorrect blade number. Please provide the right one, corresponding to your blade."
)
command = "blade={}".format(value)
self.send_command(command)
output = self.read()
self.get_blade_type()
return output
[docs] def get_blade_type(self):
"""
Return the blade type (see section 6.4 and 8.2 for more
details).
Returns: bytes: Answer to sent command. Typically consists of
command and value.
"""
command = "blade?"
self.send_command(command)
self.blade_type = self.parse_data(command, self.read())
return self.blade_type
[docs] def set_harmonic_multiplier(self, value: int):
"""
Set Harmonic Multiplier applied to external reference frequency
(1-15).
Args:
value (int): 1-15.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "nharmonic={}".format(value)
self.send_command(command)
return self.read()
[docs] def get_harmonic_multiplier(self):
"""
Returns the Harmonic Multiplier.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "nharmonic?"
self.send_command(command)
return self.read()
[docs] def set_harmonic_divider(self, value: int):
"""
Set the Harmonic Divider applied to external reference frequency
(1-15).
Args:
value (float): 1-15.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "dharmonic={}".format(value)
self.send_command(command)
output = self.read()
harmonic_divider = self.get_harmonic_divider()
logger.info(
"Chopper {} harmonic divider is {}.".format(self.name, harmonic_divider)
)
return output
[docs] def get_harmonic_divider(self):
"""
Returns the Harmonic Divider.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "dharmonic?"
self.send_command(command)
self.harmonic_divider = self.parse_data(command, self.read())
logger.info(
"Chopper {} harmonic divider is {}.".format(
self.name, self.harmonic_divider
)
)
return self.harmonic_divider
[docs] def set_phase(self, value: int):
"""
Set the Phase adjust (0-360°).
Args:
value (int): Phase in degrees (0-360°).
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "phase={}".format(value)
self.send_command(command)
output = self.read()
phase = self.get_phase()
logger.info("Chopper {} phase is set to {}.".format(self.name, phase))
return output
[docs] def get_phase(self):
"""
Returns the Phase adjust.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "phase?"
self.send_command(command)
self.phase = self.parse_data(command, self.read())
logger.info("Chopper {} phase is {}.".format(self.name, self.phase))
return self.phase
[docs] def set_enable(self, value: int):
"""
Set Enable (0=disabled, 1=enabled).
Args:
value (int): (0=disabled, 1=enabled).
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "enable={}".format(value)
self.send_command(command)
output = self.read()
state = self.get_enable()
logger.info("Chopper {} was set to {}.".format(self.name, state))
return output
[docs] def get_enable(self):
"""
Get Enable.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "enable?"
self.send_command(command)
self.enabled = self.parse_data(command, self.read())
logger.info("Chopper {} enable state is {}.".format(self.name, self.enabled))
return self.enabled
[docs] def set_reference(self, value: int):
"""
Set the reference mode (See blade dependent reference input 8.3)
Args:
value (int): Blade Dependent Reference Input.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "ref={}".format(value)
self.send_command(command)
return self.read()
[docs] def get_reference(self):
"""
Returns the reference mode.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "ref?"
self.send_command(command)
return self.read()
[docs] def set_ref_output(self, value: int):
"""
Set the output reference mode (See blade dependent reference
output 8.4)
Args:
value (int): Blade Dependent Reference Output.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "output={}".format(value)
self.send_command(command)
return self.read()
[docs] def get_ref_output(self):
"""
Returns the output reference mode.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "output?"
self.send_command(command)
return self.read()
[docs] def set_on_cycle(self, value: int):
"""
Set On Cycle (1-50%). #! might not work for MC-2000
Args:
value (int): Duty cycle 1-50%
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "oncycle={}".format(value)
self.send_command(command)
return self.read()
[docs] def get_on_cycle(self):
"""
Get On Cycle. #! might not work for MC-2000
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "oncycle?"
self.send_command(command)
return self.read()
[docs] def set_display_intensity(self, value: int):
"""
Set the Display Intensity (1-10).
Args:
value (int): Display intensity (1-10)
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "intensity={}".format(value)
self.send_command(command)
return self.read()
[docs] def get_display_intensity(self):
"""
Returns the Display Intensity.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "intensity?"
self.send_command(command)
return self.read()
[docs] def get_reference_frequency(self):
"""
Returns the current supplied external reference frequency.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "input?"
self.send_command(command)
return self.read()
[docs] def restore(self):
"""
Restore the factory default parameters.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "restore"
self.send_command(command)
return self.read()
[docs] def get_verbose(self):
"""
Returns the verbose mode.
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "verbose?"
self.send_command(command)
return self.read()
[docs] def set_verbose(self, value: int):
"""
When verbose mode is set to 1, status messages are output on the
USB.
Args:
value (int): Display intensity (1-10)
Returns:
bytes: Answer to sent command. Typically consists of command
and value.
"""
command = "verbose={}".format(value)
self.send_command(command)
return self.read()
[docs] def end(self):
self.set_enable(0)
logger.info("Chopper {} was turned off.".format(self.name))
self.ser.close()
def __enter__(self):
return self
def __exit__(self, type, value, tb):
self.end() # ? should work fine
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
port = "COM6"
chopper = ChopperController(port)
a = chopper.set_enable(1)
print(a)
a = chopper.set_enable(0)
print(chopper.get_enable())
b = chopper.set_harmonic_divider(1)
print(b)
c = chopper.set_phase(359)
print(c)
d = chopper.set_frequency(1000)
print(type(d))
time.sleep(5)
chopper.end()