# Helper modules
from typing import List, Tuple
import logging
# Import relevant modules
import nidaqmx as ni
from nidaqmx import task as ni_task
import time
# Set up logger
logger = logging.getLogger(__name__)
[docs]class Shutter:
"""
This class can be used to open and close shutters by applying 5V or
0V to their inputs.
It is assumed that a National Instruments nidaqmx capable device is
used to provide the output voltage. For this a digital output port
is used. By default, opening means applying 5V, while closed implies
0 V. The time it takes for a shutter to open/ close is around 10 ms.
Waiting 50 ms should be enough to ensure that the process of
opening/ closing finishes.
Args:
device_name (str): Name of device on which the appropriate
voltage-out port is located. The name can be found and
configured in the National Instruments Software: 'Measurement
and Automation Explorer' (MAX). The device name should also be
displayed in a pop-up when plugging in the device.
* E.g.: "Dev0", "Dev1" etc.
channel (str): Name of channel, that is used to send signal.
* E.g.: "do0", "do1", "PFI0", "PFI1" etc.
waiting_time (float, optional): Time in milliseconds to wait for
the shutter to close/open. Defaults to 50 ms.
default_position (bool, optional): Set to True if default state
is open. Set to False if default state is closed. Defaults to
False.
open_electrical_state (bool, optional): Set to True if to open
the shutter the a digital high signal (5V) needs to be sent. Set
to False if to open shutter a digital low (0V) needs to be
applied. Defaults to True.
name (str, optional): Name / Identifier to give to this shutter.
This is relevant for log statements, especially when there is
more than one shutter in the setup. Defaults to "XYZ Shutter".
Attributes:
task (object): Represents a DAQmx Task object, through which all
communication with device is managed.
current_position (bool): True when shutter is open. False when
shutter is closed.
channel (object): DAQmx channel object representing the output
channel.
References:
| https://nidaqmx-python.readthedocs.io/en/latest/index.html
| https://nidaqmx-python.readthedocs.io/en/latest/task.html
"""
def __init__(
self,
device_name: str,
channel: str,
waiting_time: float = 50,
default_position: bool = False,
open_electrical_state: bool = True,
name: str = "XYZ Shutter",
) -> "Shutter":
# Set attributes
self.name = name
self.default_position = default_position
self.open_electrical_state = open_electrical_state
self.waiting_time = waiting_time
self.current_position = None
# Initialise nidaqmx task object
self.task = ni_task.Task(self.name)
logger.info("Initialized nidaqmx task for {}.".format(self.name))
# Connect to appropriate digital out channel
self.channel = self.task.do_channels.add_do_chan(
r"/{}/{}".format(device_name, channel)
) # See nidaqmx.task.do_channel documentation
logger.info(
"Set {} as digital output channel for {}.".format(self.channel, self.name)
)
# Set shutter to default state
self.move_to_default_position()
def __wait(self):
"""
Puts thread to sleep for specified waiting time in milliseconds
using the time module.
"""
logger.info(
"Waiting {} ms for {} to complete movement.".format(
self.waiting_time, self.name
)
)
time.sleep(self.waiting_time / 1000) # Put thread to sleep
logger.info("Waiting time for {} completed.".format(self.name))
[docs] def open(self):
"""
Opens shutter. Then waits for specified waiting time.
"""
# Check whether if shutter is already opened
if self.current_position == True:
logger.info("{} is already opened. Doing nothing.".format(self.name))
return # Exit function / Skip rest of code
if (
self.open_electrical_state == True
): # Decide whether high or low needs to be sent.
self.task.write(True)
logger.info("{} is opening by sending 5V signal.".format(self.name))
elif self.open_electrical_state == False:
self.task.write(False)
logger.info("{} is opening by sending 0V signal.".format(self.name))
self.__wait()
self.current_position = True
[docs] def close(self):
"""
Closes shutter. Then waits for specified waiting time.
"""
# Check whether if shutter is already closed
if self.current_position == False:
logger.info("{} is already closed. Doing nothing.".format(self.name))
return # Exit function / Skip rest of code
if (
self.open_electrical_state == True
): # Decide whether high or low needs to be sent.
self.task.write(False)
logger.info("{} is closing by sending 0V signal.".format(self.name))
elif self.open_electrical_state == False:
self.task.write(True)
logger.info("{} is closing by sending 5V signal.".format(self.name))
self.__wait()
self.current_position = False
[docs] def move_to_default_position(self):
"""
Moves shutter to its default position.
"""
logger.info("Moving {} to default position.".format(self.name))
# Manage initialization by setting current position to opposite
# of default position.
if self.current_position == None:
self.current_position = not self.default_position
# Move shutter to default postion
if self.default_position == True:
self.open()
elif self.default_position == False:
self.close()
[docs] def end(self):
"""
Moves shutter to default position and ends nidaqmx task.
"""
logger.info("Moving {} to default position to end task.".format(self.name))
self.move_to_default_position()
self.task.close()
logger.info("Connection to {} terminated.".format(self.name))
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.end()