diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 1fd78cc..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,409 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1619125880800
-
-
- 1619125880800
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/communication/communicationport.py
- 55
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/spectrometers/oceaninsight.py
- 679
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/__main__.py
- 15
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/powermeters/powermeterdevice.py
- 37
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/devicemanager.py
- 314
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/tests/testPhysicalDevice.py
- 235
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/echodevice.py
- 25
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/echodevice.py
- 24
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/communication/serialport.py
- 116
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/cameras/camera.py
- 105
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/tests/testPhysicalDevice.py
- 46
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/tests/testIntellidrive.py
- 29
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/tests/testIntellidrive.py
- 66
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/communication/serialport.py
- 193
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/motion/intellidrivedevice.py
- 85
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/motion/intellidrivedevice.py
- 115
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/spectrometers/base.py
- 262
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/devicemanager.py
- 215
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/tests/testPhysicalDevice.py
- 298
-
-
-
- file://$PROJECT_DIR$/hardwarelibrary/tests/testPhysicalDevice.py
- 293
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/hardwarelibrary/communication/communicationport.py b/hardwarelibrary/communication/communicationport.py
index 2952c88..4fd2a57 100644
--- a/hardwarelibrary/communication/communicationport.py
+++ b/hardwarelibrary/communication/communicationport.py
@@ -73,14 +73,12 @@ def readString(self, endPoint=None) -> str:
except CommunicationReadTimeout as err:
raise CommunicationReadTimeout("Only obtained {0}".format(data))
- string = data.decode(encoding='utf-8')
-
- return string
+ return data.decode('utf-8', 'replace')
def writeString(self, string, endPoint=None) -> int:
nBytes = 0
with self.portLock:
- data = bytearray(string, "utf-8")
+ data = bytearray(string, 'utf-8', 'replace')
nBytes = self.writeData(data, endPoint)
return nBytes
diff --git a/hardwarelibrary/communication/debugport.py b/hardwarelibrary/communication/debugport.py
index b7ebe45..a22ab0b 100644
--- a/hardwarelibrary/communication/debugport.py
+++ b/hardwarelibrary/communication/debugport.py
@@ -56,18 +56,19 @@ def bytesAvailable(self, endPoint=0):
return len(self.outputBuffers[endPoint])
def flush(self):
- self.buffers = [bytearray(),bytearray()]
+ self.inputBuffers = [bytearray()]
+ self.outputBuffers = [bytearray()]
def readData(self, length, endPoint=None):
if endPoint is None:
- endPointIndex = 0
+ endPoint = 0
with self.portLock:
time.sleep(self.delay*random.random())
data = bytearray()
for i in range(0, length):
- if len(self.outputBuffers[endPointIndex]) > 0:
- byte = self.outputBuffers[endPointIndex].pop(0)
+ if len(self.outputBuffers[endPoint]) > 0:
+ byte = self.outputBuffers[endPoint].pop(0)
data.append(byte)
else:
raise CommunicationReadTimeout("Unable to read data")
@@ -76,12 +77,12 @@ def readData(self, length, endPoint=None):
def writeData(self, data, endPoint=None):
if endPoint is None:
- endPointIndex = 0
+ endPoint = 0
with self.portLock:
- self.inputBuffers[endPointIndex].extend(data)
+ self.inputBuffers[endPoint].extend(data)
- self.processInputBuffers(endPointIndex=endPointIndex)
+ self.processInputBuffers(endPoint)
return len(data)
diff --git a/hardwarelibrary/communication/serialport.py b/hardwarelibrary/communication/serialport.py
index dc3a7c7..151403c 100644
--- a/hardwarelibrary/communication/serialport.py
+++ b/hardwarelibrary/communication/serialport.py
@@ -62,7 +62,7 @@ def matchAnyPort(cls, idVendor=None, idProduct=None, serialNumber=None):
return None
@classmethod
- def matchPorts(cls, idVendor=None, idProduct=None, serialNumber=None):
+ def matchPortObjects(cls, idVendor=None, idProduct=None, serialNumber=None):
# We must provide idVendor, idProduct and serialNumber
# or idVendor and idProduct
# or idVendor
@@ -81,7 +81,7 @@ def matchPorts(cls, idVendor=None, idProduct=None, serialNumber=None):
pass
- # It sometimes happens on macOS that the ports are "doubled" because two user-space DriverExtension
+ # It sometimes happens on macOS that the ports are "doubled" because two user-space DriverExtension
# prepare a port (FTDI and Apple's for instance). If that is the case, then we try to remove duplicates
portObjects = []
@@ -102,14 +102,18 @@ def matchPorts(cls, idVendor=None, idProduct=None, serialNumber=None):
if re.search(serialNumber, port.serial_number, re.IGNORECASE):
portObjects.append(port)
+ return portObjects
+ @classmethod
+ def matchPorts(cls, idVendor=None, idProduct=None, serialNumber=None):
+ portObjects = cls.matchPortObjects(idVendor, idProduct, serialNumber)
ports = []
# portsAlreadyAdded = []
for port in portObjects:
# uniqueIdentifier = (port.vid, port.pid, port.serial_number)
- # if not (uniqueIdentifier in portsAlreadyAdded):
+ # if not (uniqueIdentifier in portsAlreadyAdded):
ports.append(port.device)
- # portsAlreadyAdded.append(uniqueIdentifier)
+ # portsAlreadyAdded.append(uniqueIdentifier)
return ports
@@ -191,7 +195,7 @@ def readString(self, endPoint=0):
with self.portLock:
data = self.port.read_until(expected=self.terminator)
- return data.decode()
+ return data.decode('utf-8', 'replace')
def readData(self, length, endPoint=0) -> bytearray:
with self.portLock:
diff --git a/hardwarelibrary/communication/usbport.py b/hardwarelibrary/communication/usbport.py
index 8f6f357..52f34b7 100644
--- a/hardwarelibrary/communication/usbport.py
+++ b/hardwarelibrary/communication/usbport.py
@@ -163,6 +163,6 @@ def readString(self, endPoint=None) -> str:
try:
data += self.readData(length=1, endPoint=endPoint)
if data[-1] == 10: # How to write '\n' ?
- return data.decode(encoding='utf-8')
+ return data.decode('utf-8', 'replace')
except Exception as err:
raise IOError("Unable to read string terminator: {0}".format(err))
diff --git a/hardwarelibrary/irises/__init__.py b/hardwarelibrary/irises/__init__.py
new file mode 100644
index 0000000..c39ad64
--- /dev/null
+++ b/hardwarelibrary/irises/__init__.py
@@ -0,0 +1,3 @@
+
+from .irisdevice import IrisDevice
+from .uniblitzai25device import UniblitzAI25Device
diff --git a/hardwarelibrary/irises/driverAI25/README.md b/hardwarelibrary/irises/driverAI25/README.md
new file mode 100644
index 0000000..4ba2fdf
--- /dev/null
+++ b/hardwarelibrary/irises/driverAI25/README.md
@@ -0,0 +1,10 @@
+# driverAI25
+
+Arduino Uno driver to control the Uniblitz AI25 Auto Iris. Requires a 12V 2A power supply (2.1 x 5.5 mm barrel jack).
+
+You need to install `NeoSWSerial` in the Arduino IDE Library Manager.
+
+Reference manual: https://www.uniblitz.com/wp-content/uploads/2021/03/ai25-direct-control-v1-2.pdf
+
+Pinout:
+
diff --git a/hardwarelibrary/irises/driverAI25/driverAI25.ino b/hardwarelibrary/irises/driverAI25/driverAI25.ino
new file mode 100644
index 0000000..e86b335
--- /dev/null
+++ b/hardwarelibrary/irises/driverAI25/driverAI25.ino
@@ -0,0 +1,44 @@
+/*
+ * Uniblitz AI25 Auto Iris driver v1.1
+ * for Arduino Uno/Nano/Micro
+ *
+ * Pin 2 -> AI25 Interrupt (Micro USB3 pin 9)
+ * Pin 3 -> AI25 RX (Micro USB3 pin 7)
+ * Pin 4 -> AI25 TX (Micro USB3 pin 6)
+ */
+
+#include
+
+const byte interruptPin = 2;
+NeoSWSerial irisSerial(4, 3); // Driver RX/TX -> AI25 TX/RX
+String command; // input buffer
+
+void setup()
+{
+ Serial.begin(9600);
+ irisSerial.begin(9600);
+ pinMode(2, OUTPUT);
+ while (!Serial); // Wait for serial port to connect; needed for native USB only
+ Serial.println("driverAI25 Ready");
+}
+
+void loop()
+{
+ if (Serial.available())
+ {
+ command = Serial.readStringUntil('\n');
+
+ if (command == "V")
+ {
+ Serial.println("driverAI25 v1.1");
+ return;
+ }
+
+ digitalWrite(interruptPin, HIGH);
+ delay(2);
+ digitalWrite(interruptPin, LOW);
+
+ irisSerial.println(command); // send command
+ Serial.println(irisSerial.readStringUntil('\n')); // return reply
+ }
+}
diff --git a/hardwarelibrary/irises/driverAI25/driverAI25_pinout.jpg b/hardwarelibrary/irises/driverAI25/driverAI25_pinout.jpg
new file mode 100644
index 0000000..dbbe6f6
Binary files /dev/null and b/hardwarelibrary/irises/driverAI25/driverAI25_pinout.jpg differ
diff --git a/hardwarelibrary/irises/irisdevice.py b/hardwarelibrary/irises/irisdevice.py
new file mode 100644
index 0000000..7dac7dd
--- /dev/null
+++ b/hardwarelibrary/irises/irisdevice.py
@@ -0,0 +1,118 @@
+#
+# irisdevice.py
+#
+
+from enum import Enum
+from hardwarelibrary.physicaldevice import *
+from hardwarelibrary.notificationcenter import NotificationCenter, Notification
+
+
+class IrisNotification(Enum):
+ willMove = "willMove"
+ didMove = "didMove"
+ didGetPosition = "didGetPosition"
+
+
+class IrisDevice(PhysicalDevice):
+ def __init__(self, serialNumber: str, idProduct: int, idVendor: int):
+ super().__init__(serialNumber, idProduct, idVendor)
+ self.minStep = 0 # closed
+ self.maxStep = 2**31 # open
+ self.micronsPerStep = 1000
+ self.minAperture = -1 # microns when closed
+
+ def convertMicronsToStep(self, microns: float) -> int:
+ """ Converts an aperture size in microns to a native step position. """
+ if self.micronsPerStep == 0:
+ raise ValueError("micronsPerStep == 0")
+ if self.minAperture < 0:
+ raise ValueError("minAperture < 0")
+
+ return round((microns - self.minAperture) / self.micronsPerStep + self.minStep)
+
+ def convertStepToMicrons(self, step: int) -> float:
+ """ Converts a native step position to an aperture size in microns. """
+ if self.minAperture < 0:
+ raise ValueError("minAperture < 0")
+
+ return (step - self.minStep) * self.micronsPerStep + self.minAperture
+
+ def currentStep(self) -> int:
+ """ Returns the current native position. """
+ step = self.doGetCurrentStep()
+ NotificationCenter().postNotification(IrisNotification.didGetPosition, notifyingObject=self, userInfo=step)
+ return step
+
+ def aperture(self) -> float:
+ """ Returns the current aperture size in microns. """
+ return self.convertStepToMicrons(self.currentStep())
+
+ def home(self):
+ NotificationCenter().postNotification(IrisNotification.willMove, notifyingObject=self)
+ self.doHome()
+ NotificationCenter().postNotification(IrisNotification.didMove, notifyingObject=self)
+
+ def moveTo(self, step: int):
+ NotificationCenter().postNotification(IrisNotification.willMove, notifyingObject=self, userInfo=step)
+ self.doMoveTo(step)
+ NotificationCenter().postNotification(IrisNotification.didMove, notifyingObject=self, userInfo=step)
+
+ def moveToMicrons(self, microns):
+ self.moveTo(self.convertMicronsToStep(microns))
+
+ def moveBy(self, steps: int):
+ NotificationCenter().postNotification(IrisNotification.willMove, notifyingObject=self, userInfo=steps)
+ self.doMoveBy(steps)
+ NotificationCenter().postNotification(IrisNotification.didMove, notifyingObject=self, userInfo=steps)
+
+ def moveByMicrons(self, microns):
+ self.moveBy(round(microns / self.micronsPerStep))
+
+ def isValidStep(self, step: int) -> bool:
+ return min(self.minStep, self.maxStep) < step < max(self.minStep, self.maxStep)
+
+ def isValidAperture(self, microns: float) -> bool:
+ return self.isValidStep(self.convertMicronsToStep(microns))
+
+ def doGetCurrentStep(self) -> int:
+ raise NotImplementedError
+
+ def doHome(self):
+ raise NotImplementedError
+
+ def doMoveTo(self, step):
+ raise NotImplementedError
+
+ def doMoveBy(self, steps):
+ raise NotImplementedError
+
+
+class DebugIrisDevice(IrisDevice):
+ classIdProduct = 0xFFFD
+ classIdVendor = debugClassIdVendor
+
+ def __init__(self):
+ super().__init__("debug", DebugIrisDevice.classIdProduct, DebugIrisDevice.classIdVendor)
+ self.minStep = 0 # iris closed
+ self.maxStep = 100 # iris open
+ self.micronsPerStep = 100
+ self.minAperture = 0 # microns when closed
+ self.lastSetStep = 0
+
+ def doHome(self):
+ self.lastSetStep = 0
+
+ def doGetCurrentStep(self):
+ return self.lastSetStep
+
+ def doMoveTo(self, step):
+ self.lastSetStep = step
+
+ def doMoveBy(self, steps):
+ self.lastSetStep += steps
+
+ def doInitializeDevice(self):
+ pass
+
+ def doShutdownDevice(self):
+ pass
diff --git a/hardwarelibrary/irises/uniblitzai25device.py b/hardwarelibrary/irises/uniblitzai25device.py
new file mode 100644
index 0000000..7fee632
--- /dev/null
+++ b/hardwarelibrary/irises/uniblitzai25device.py
@@ -0,0 +1,138 @@
+#
+# uniblitzai25device.py
+#
+# Allows controlling the Uniblitz AI25 Auto Iris using a custom driver.
+# See "driverAI25" in the "irises" folder and "ai25-direct-control-v1-2.pdf" in the "manuals" folder.
+#
+
+from serial.tools.list_ports_common import ListPortInfo
+from hardwarelibrary.irises.irisdevice import *
+from hardwarelibrary.communication.communicationport import CommunicationPort
+from hardwarelibrary.communication.serialport import SerialPort
+from hardwarelibrary.communication.debugport import DebugPort
+
+
+class UniblitzAI25Device(IrisDevice):
+ classIdVendor = 0x2341 # Arduino
+
+ def __init__(self, serialNumber: str = None):
+ super().__init__(serialNumber=serialNumber, idVendor=self.classIdVendor, idProduct=self.classIdProduct)
+
+ self.minStep = 54 # iris closed
+ self.maxStep = 0 # iris open
+ self.micronsPerStep = -440
+ self.minAperture = 1500 # microns when closed
+ # self.incrementMicrons = 440
+
+ self.port: CommunicationPort = None
+ self.isHomed = False
+ self.lastSetStep = 0
+
+ def __del__(self):
+ self.doShutdownDevice()
+
+ def doInitializeDevice(self):
+ try:
+ if 'debug' in self.serialNumber:
+ self.port = self.DebugSerialPort()
+ self.port.open()
+ else:
+ try:
+ portObject: ListPortInfo = SerialPort.matchPortObjects(self.idVendor, self.idProduct, self.serialNumber)[0]
+ except:
+ raise Exception("No Uniblitz Iris connected")
+
+ self.idVendor = portObject.vid
+ self.idProduct = portObject.pid
+ self.serialNumber = portObject.serial_number
+
+ portPath = portObject.device
+ self.port = SerialPort(portPath=portPath)
+
+ if self.port is None:
+ raise Exception("Cannot allocate port " + portPath)
+
+ self.port.open(baudRate=9600, timeout=3)
+
+ self.port.readMatchingGroups('^(driverAI25 )?Ready\r')
+ self.isHomed = False
+ # self.doMoveBy(-1)
+ # self.doMoveBy(1)
+
+ except Exception as err:
+ self.doShutdownDevice()
+ raise PhysicalDevice.UnableToInitialize(err)
+
+ def doShutdownDevice(self):
+ if self.port and self.port.isOpen:
+ self.port.close()
+ self.port = None
+
+ def sendCommand(self, command: str):
+ """ Writes a command to the AI25 driver. It will initialize the device if needed. On failure, it will warn and shutdown. """
+ if self.port is None or not self.port.isOpen:
+ self.initializeDevice()
+
+ self.port.flush()
+ self.port.writeStringExpectMatchingString(command + '\r\n', replyPattern='^!\r')
+
+ def doGetCurrentStep(self):
+ return self.lastSetStep
+
+ def doHome(self):
+ self.sendCommand('H')
+ self.lastSetStep = 0
+ self.isHomed = True
+
+ def doMoveTo(self, step: int):
+ self.sendCommand('{}{:02d}'.format('M' if self.isHomed else 'G', step))
+ self.lastSetStep = step
+ self.isHomed = True
+
+ def doMoveBy(self, steps: int):
+ increment = -1 if steps < 0 else 1
+ command = 'I' if steps < 0 else 'D' # decrease step = [I]ncrease aperture
+ for _ in range(abs(steps)):
+ self.sendCommand(command)
+ self.lastSetStep += increment
+
+ class DebugSerialPort(DebugPort):
+ def __init__(self):
+ super().__init__()
+ self.minStep = 54
+ self.maxStep = 0
+ self.currentStep = 0
+
+ def open(self):
+ super().open()
+ self.writeToOutputBuffer(bytearray('driverAI25 Ready\r\n', 'utf-8'), 0)
+
+ def processInputBuffers(self, endPointIndex):
+ command = self.inputBuffers[endPointIndex].decode('utf-8')
+ reply = '!'
+
+ # Home
+ if re.match('^H\r', command):
+ self.currentStep = 0
+
+ # Increment
+ elif re.match('^I\r', command):
+ self.currentStep = min(self.currentStep - 1, self.maxStep)
+
+ # Decrement
+ elif re.match('^D\r', command):
+ self.currentStep = max(self.currentStep + 1, self.minStep)
+
+ # Move
+ elif match := re.match('^[MG](\\d\\d)\r', command):
+ target = int(match.group(1))
+ if min(self.minStep, self.maxStep) <= target <= max(self.minStep, self.maxStep):
+ self.currentStep = target
+ time.sleep(0.5)
+ else:
+ reply = '?'
+ else:
+ reply = '?'
+
+ self.writeToOutputBuffer(bytearray(reply + '\r\n', 'utf-8'), endPointIndex)
+ self.inputBuffers[endPointIndex] = bytearray()
diff --git a/hardwarelibrary/manuals/ai25-direct-control-v1-2.pdf b/hardwarelibrary/manuals/ai25-direct-control-v1-2.pdf
new file mode 100644
index 0000000..f9a58a4
Binary files /dev/null and b/hardwarelibrary/manuals/ai25-direct-control-v1-2.pdf differ
diff --git a/hardwarelibrary/motion/sutterdevice.py b/hardwarelibrary/motion/sutterdevice.py
index 5625bf1..f9b4320 100644
--- a/hardwarelibrary/motion/sutterdevice.py
+++ b/hardwarelibrary/motion/sutterdevice.py
@@ -49,7 +49,7 @@ def doInitializeDevice(self):
self.port.open(baudRate=128000, timeout=10)
if self.port is None:
- raise PhysicalDevice.UnableToInitialize("Cannot allocate port {0}".format(self.portPath))
+ raise PhysicalDevice.UnableToInitialize("Cannot allocate port {0}".format(portPath))
self.positionInMicrosteps()
diff --git a/hardwarelibrary/physicaldevice.py b/hardwarelibrary/physicaldevice.py
index 4fec4c1..3e612f8 100644
--- a/hardwarelibrary/physicaldevice.py
+++ b/hardwarelibrary/physicaldevice.py
@@ -69,7 +69,7 @@ def vidpids(cls):
@classmethod
def isCompatibleWith(cls, serialNumber, idProduct, idVendor):
for compatibleIdVendor, compatibleIdProduct in cls.vidpids():
- if idVendor == compatibleIdVendor and idProduct == compatibleIdProduct:
+ if idVendor == compatibleIdVendor and (idProduct == compatibleIdProduct or compatibleIdProduct is None):
return True
return False
diff --git a/hardwarelibrary/sources/cobolt.py b/hardwarelibrary/sources/cobolt.py
index fa87712..22c15c9 100644
--- a/hardwarelibrary/sources/cobolt.py
+++ b/hardwarelibrary/sources/cobolt.py
@@ -177,7 +177,7 @@ def read(self, length) -> bytearray:
def write(self, data:bytearray) -> int :
with globalLock:
- string = data.decode('utf-8')
+ string = data.decode('utf-8', 'replace')
match = re.search("pa\\?", string)
if match is not None:
diff --git a/hardwarelibrary/tests/testIrisDevice.py b/hardwarelibrary/tests/testIrisDevice.py
new file mode 100644
index 0000000..7da49f7
--- /dev/null
+++ b/hardwarelibrary/tests/testIrisDevice.py
@@ -0,0 +1,139 @@
+import env
+import unittest
+
+from hardwarelibrary.irises.irisdevice import *
+from hardwarelibrary.irises.uniblitzai25device import UniblitzAI25Device
+from hardwarelibrary.notificationcenter import NotificationCenter
+
+
+class BaseTestCases:
+ class TestIrisDevice(unittest.TestCase):
+ device: IrisDevice
+
+ def setUp(self):
+ # Set self.device in subclass
+ self.willNotificationReceived = False
+ self.didNotificationReceived = False
+ if self.device is None:
+ raise (unittest.SkipTest("No device defined in subclass of BaseTestCase"))
+
+ try:
+ self.device.initializeDevice()
+ except:
+ raise unittest.SkipTest("No devices connected")
+
+ def tearDown(self):
+ self.device.shutdownDevice()
+
+ def testCurrentStep(self):
+ step = self.device.currentStep()
+ self.assertIsNotNone(step)
+ self.assertTrue(step >= 0)
+
+ def testAperture(self):
+ aperture = self.device.aperture()
+ self.assertIsNotNone(aperture)
+ self.assertTrue(aperture >= 0)
+
+ def testMove(self):
+ target = self.device.minStep + (self.device.maxStep - self.device.minStep) // 3
+ self.device.moveTo(target)
+ self.assertEqual(self.device.currentStep(), target)
+
+ def testMoveBy(self):
+ step = self.device.currentStep()
+ target = 2
+ self.device.moveBy(target)
+ self.assertEqual(self.device.currentStep(), step + target)
+
+ def testMoveToMicrons(self):
+ target = self.device.minAperture + self.device.micronsPerStep * ((self.device.maxStep - self.device.minStep) // 2)
+ self.device.moveToMicrons(target)
+ self.assertEqual(self.device.aperture(), target)
+
+ def testMoveByMicrons(self):
+ aperture = self.device.aperture()
+ target = aperture + self.device.micronsPerStep * 2
+ self.device.moveToMicrons(target)
+ self.assertEqual(self.device.aperture(), target)
+
+ def testDeviceHome(self):
+ self.device.home()
+ self.assertEqual(self.device.currentStep(), 0)
+
+ def handleWill(self, notification):
+ self.willNotificationReceived = True
+
+ def handleDid(self, notification):
+ self.didNotificationReceived = True
+
+ def testPositionNotifications(self):
+ NotificationCenter().addObserver(self, method=self.handleDid, notificationName=IrisNotification.didGetPosition)
+ self.device.currentStep()
+ self.assertTrue(self.didNotificationReceived)
+ NotificationCenter().removeObserver(self)
+
+ def testDeviceMoveNotifications(self):
+ NotificationCenter().addObserver(self, method=self.handleWill, notificationName=IrisNotification.willMove)
+ NotificationCenter().addObserver(self, method=self.handleDid, notificationName=IrisNotification.didMove)
+
+ self.assertFalse(self.willNotificationReceived)
+ self.assertFalse(self.didNotificationReceived)
+
+ self.device.moveTo(self.device.minStep + (self.device.maxStep - self.device.minStep) // 2)
+
+ self.assertTrue(self.willNotificationReceived)
+ self.assertTrue(self.didNotificationReceived)
+
+ NotificationCenter().removeObserver(self)
+
+ def testDeviceMoveByNotifications(self):
+ NotificationCenter().addObserver(self, method=self.handleWill, notificationName=IrisNotification.willMove)
+ NotificationCenter().addObserver(self, method=self.handleDid, notificationName=IrisNotification.didMove)
+
+ self.assertFalse(self.willNotificationReceived)
+ self.assertFalse(self.didNotificationReceived)
+
+ self.device.moveBy(2)
+
+ self.assertTrue(self.willNotificationReceived)
+ self.assertTrue(self.didNotificationReceived)
+
+ NotificationCenter().removeObserver(self)
+
+ def testDeviceHomeNotifications(self):
+
+ NotificationCenter().addObserver(self, method=self.handleWill, notificationName=IrisNotification.willMove)
+ NotificationCenter().addObserver(self, method=self.handleDid, notificationName=IrisNotification.didMove)
+
+ self.assertFalse(self.willNotificationReceived)
+ self.assertFalse(self.didNotificationReceived)
+
+ self.device.home()
+
+ self.assertTrue(self.willNotificationReceived)
+ self.assertTrue(self.didNotificationReceived)
+
+ NotificationCenter().removeObserver(self)
+
+
+class TestDebugLinearMotionDeviceBase(BaseTestCases.TestIrisDevice):
+ def setUp(self):
+ self.device = DebugIrisDevice()
+ super().setUp()
+
+
+class TestDebugUniblitzAI25DeviceBase(BaseTestCases.TestIrisDevice):
+ def setUp(self):
+ self.device = UniblitzAI25Device("debug")
+ super().setUp()
+
+
+class TestRealUniblitzAI25DeviceBase(BaseTestCases.TestIrisDevice):
+ def setUp(self):
+ self.device = UniblitzAI25Device()
+ super().setUp()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/hardwarelibrary/tests/testLinearMotionDevice.py b/hardwarelibrary/tests/testLinearMotionDevice.py
index 7bdb9e9..45903c2 100644
--- a/hardwarelibrary/tests/testLinearMotionDevice.py
+++ b/hardwarelibrary/tests/testLinearMotionDevice.py
@@ -1,13 +1,15 @@
import env
import unittest
-from hardwarelibrary.motion.linearmotiondevice import DebugLinearMotionDevice, LinearMotionNotification
+from hardwarelibrary.motion.linearmotiondevice import *
from hardwarelibrary.motion.sutterdevice import SutterDevice
from hardwarelibrary.notificationcenter import NotificationCenter
class BaseTestCases:
class TestLinearMotionDevice(unittest.TestCase):
+ device: LinearMotionDevice
+
def setUp(self):
# Set self.device in subclass
self.willNotificationReceived = False
@@ -96,16 +98,6 @@ def testDeviceHome(self):
self.assertEqual(y, 0)
self.assertEqual(z, 0)
-
- def testDevicePosition(self):
- (x, y, z) = self.device.position()
- self.assertIsNotNone(x)
- self.assertIsNotNone(y)
- self.assertIsNotNone(z)
- self.assertTrue(x >= 0)
- self.assertTrue(y >= 0)
- self.assertTrue(z >= 0)
-
def handleWill(self, notification):
self.willNotificationReceived = True
@@ -162,20 +154,24 @@ def testDeviceHomeNotifications(self):
NotificationCenter().removeObserver(self)
+
class TestDebugLinearMotionDeviceBase(BaseTestCases.TestLinearMotionDevice):
def setUp(self):
self.device = DebugLinearMotionDevice()
super().setUp()
+
class TestDebugSutterDeviceBase(BaseTestCases.TestLinearMotionDevice):
def setUp(self):
self.device = SutterDevice("debug")
super().setUp()
+
class TestRealSutterDeviceBase(BaseTestCases.TestLinearMotionDevice):
def setUp(self):
self.device = SutterDevice()
super().setUp()
+
if __name__ == '__main__':
unittest.main()
diff --git a/setup.py b/setup.py
index 1e485f2..30bf792 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@
keywords='hardware devices usb communication app control spectrometer powermeter camera',
packages=setuptools.find_packages(),
install_requires=['numpy','matplotlib','PySerial','PyUSB','pyftdi','LabJackPython'],
- python_requires='>=3.7',
+ python_requires='>=3.8',
package_data = {
# If any package contains *.txt or *.rst files, include them:
'': ['*.png'],
@@ -44,7 +44,7 @@
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
'Operating System :: OS Independent'
]