diff --git a/.idea/PyHardwareLibrary.iml b/.idea/PyHardwareLibrary.iml index 403d001..c68be6c 100644 --- a/.idea/PyHardwareLibrary.iml +++ b/.idea/PyHardwareLibrary.iml @@ -8,7 +8,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index d1e22ec..dc9ea49 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 1fd78cc..5541168 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,11 @@ - + + + + + @@ -81,7 +85,7 @@ - + - + - + - + - + - + - - - - - + + + + + - - - - - + + + + + @@ -245,6 +249,7 @@ + @@ -383,7 +388,7 @@ - + @@ -395,6 +400,7 @@ + diff --git a/hardwarelibrary/__init__.py b/hardwarelibrary/__init__.py index 634f582..a82bc9b 100644 --- a/hardwarelibrary/__init__.py +++ b/hardwarelibrary/__init__.py @@ -14,6 +14,7 @@ import hardwarelibrary.spectrometers import hardwarelibrary.oscilloscope import hardwarelibrary.motion +import hardwarelibrary.photoncounters # import hardwarelibrary.cameras #import sources #TODO: Not much to see here yet \ No newline at end of file diff --git a/hardwarelibrary/photoncounters/hamamatsu.py b/hardwarelibrary/photoncounters/hamamatsu.py new file mode 100644 index 0000000..6c2c1bc --- /dev/null +++ b/hardwarelibrary/photoncounters/hamamatsu.py @@ -0,0 +1,112 @@ +from enum import Enum, IntEnum +import hardwarelibrary.utils +from hardwarelibrary.notificationcenter import NotificationCenter +from hardwarelibrary.physicaldevice import PhysicalDevice +from hardwarelibrary.communication.usbport import * +from struct import * + +class HamamatsuH11890Device(PhysicalDevice): + classIdVendor = 0x0661 + classIdProduct = 0x3705 + + class BadCommand(Exception): + pass + + class Overload(Exception): + pass + + def __init__(self): + PhysicalDevice.__init__(self, serialNumber="*", idVendor=0x0661, idProduct = 0x3705) + + def doInitializeDevice(self): + self.port = USBPort(idVendor=0x0661, idProduct = 0x3705) + self.port.open() + self.stop_counting() # We must stop counting first + self.turn_on() + self.set_integration_time(time_in_10us=1000) + + def doShutdownDevice(self): + self.stop_counting() + self.turn_off() + self.port.close() + + def sendCommand(self, commandName, payload=None): + commandInt = None + if payload is None: + commandData = commandName.encode("utf-8") + else: + commandInt = ord(commandName) + commandData = pack(" 0) + self.device.stop_counting() + + def testStartWaitFetchAllStop(self): + self.device.set_repetition(10) + self.device.set_integration_time(10000) + self.device.start_counting() + counts = self.device.fetchAll(maxIndex=10) + for i, (idx, c) in enumerate(counts): + self.assertEqual(i, idx) + self.assertTrue(c > 0,"{0}".format(c)) + self.assertTrue(c & 0x8000 == 0) + self.device.stop_counting() + + def testStopStop(self): + self.device.stop_counting() + self.device.stop_counting() + + def testTurnOnAndOff(self): + self.device.turn_on() + self.device.turn_off() + + def testSetDefaultVoltage(self): + print(self.device.set_high_voltage()) + _, actualVoltage = self.device.get_high_voltage() + self.assertEqual(actualVoltage, 1000) + + def testSetNullVoltage(self): + self.device.set_high_voltage(0) + _, actualVoltage = self.device.get_high_voltage() + self.assertEqual(actualVoltage, 0) + + def testSetSomeVoltage(self): + self.device.set_high_voltage(800) + _, actualVoltage = self.device.get_high_voltage() + self.assertEqual(actualVoltage, 800) + + def testSetDefaultManuallyOnlyD(self): + with self.assertRaises(HamamatsuH11890Device.BadCommand): + self.device.sendCommand('D') # it's really DV + + +if __name__ == '__main__': + unittest.main() diff --git a/hardwarelibrary/tests/testHamamatsuSerial.py b/hardwarelibrary/tests/testHamamatsuSerial.py new file mode 100644 index 0000000..fdd7654 --- /dev/null +++ b/hardwarelibrary/tests/testHamamatsuSerial.py @@ -0,0 +1,342 @@ +import env +import unittest +from struct import * + +from hardwarelibrary.communication.usbport import * + +class TestHamamatsuUSBPortBase(unittest.TestCase): + def setUp(self): + self.port = USBPort(idVendor=0x0661, idProduct = 0x3705) + self.assertIsNotNone(self.port) + self.port.open() + self.assertTrue(self.port.isOpen) + + self.port.writeData(b'\r') + self.port.flush() + + def tearDown(self): + # self.port.writeData(b"ZV") + # self.assertEqual("ZV", self.readStringFromPMT()) + self.port.close() + + + def test01CreatePort(self): + self.assertIsNotNone(self.port) + + def test02OpenPort(self): + self.assertTrue(self.port.isOpen) + + def test03SendDVCommand(self): + """ + This helped me figure out that the readData command would always + read 64 bytes. + """ + self.port.writeString(string="DV") + count = 64 + while count > 0: + try: + print(count, self.port.readData(1)) + except Exception as err: + print(err) + break + count = count -1 + + def test04SendDVZVCommand(self): + """ + This never worked: ZV is not a good command. + """ + + self.port.writeString(string="DV") + count = 64 + while count > 0: + try: + print(count, self.port.readData(1)) + except Exception as err: + print(err) + break + count = count -1 + + self.port.writeString(string="ZV") + count = 64 + while count > 0: + try: + print(count, self.port.readData(1)) + # This will print 'B', 'C' etc... + except Exception as err: + print(err) + break + count = count -1 + + def test05SendDVZeroCommand(self): + """ + + """ + + self.port.writeData(b"DV\x00") + data = self.port.readData(64) + string = data.decode("utf-8") + for i,c in enumerate(string): + if c == '\x00': + string = string[:i] + break + self.port.close() + + def test06UseFunctionValidCommand(self): + """ + I created a function to read the pmt, it is flawed + but up to here it works. + """ + self.port.writeData(b"DV") + reply = self.readStringFromPMT() + self.assertEqual("DV", reply) + + def test07UseFunctionInvalidCommand(self): + """ + I figured out with these tests that BC is bad command + """ + + self.port.writeData(b"DV\n") + reply = self.readStringFromPMT() + self.assertEqual("BC", reply) + + def test07TurnOn(self): + """ + This never worked, I understood the manual was not useful much. + I checked with various suffixes, but the DV needed to be 2 bytes + """ + + commands = self.extendCommand("DV") + self.validate(commands) + + @unittest.expectedFailure + def test07TurnOff(self): + """ + This never really worked, I figured out it ZV was not a command. + I checked with various suffixes, but but nothing worked + """= + commands = self.extendCommand("ZV") + all_commands, are_valid = self.validate(commands) + print(are_valid) + self.assertTrue(any(are_valid)) + + commands = self.extendCommand("zv") + are_valid = self.validate(commands) + self.assertTrue(any(are_valid)) + + for i, is_valid in enumerate(are_valid): + print(commands[i], is_valid) + + @unittest.skip("Long test only useful once: brute force attempting to find commands") + def testFind2Letter_Commands(self): + """ + I brute forced it: what are the 2-letter commands? + I checked all combinations and checked to see if it complained 'BC' + If it did not I assumed this meant it is a command (which is wrong, see + later). + """ + for c1 in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": + for c2 in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": + commands = self.extendCommand(c1+c2) + + replies, are_valid = self.validate(commands) + + for i, is_valid in enumerate(are_valid): + if is_valid: + print(commands[i], replies[i]) + + @unittest.skip("Long test only useful once: brute force attempting to find commands") + def testFind1Letter_Commands(self): + """ + I noticed that after 'C' I started getting stuff regularly. + I figured 'C' was starting the count like in the documentatin + but I needed to figure out how to understand the output. + """ + for c1 in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": + commands = self.extendCommand(c1) + replies, are_valid = self.validate(commands) + + for i, is_valid in enumerate(are_valid): + if is_valid: + print(commands[i], replies[i]) + + @unittest.skip("Long test only useful once: brute force attempting to find commands") + def testFind1Byte_Commands(self): + """ + Just in case, checked binary but no: nothing new + """ + valid = [] + for c1 in range(128): + print("{0:x}".format(c1)) + if chr(c1) == 'D': + continue + + commands = [chr(c1)] + + replies, are_valid = self.validate(commands) + + for i, is_valid in enumerate(are_valid): + if is_valid: + print(commands[i], replies[i]) + valid.append(commands[i]) + + print(valid) + + @unittest.skip("Long test only useful once: brute force attempting to find commands") + def testFind2Byte_Commands(self): + """ + Same + """ + for c1 in range(256): + print("-",c1) + for c2 in range(256): + commands = self.extendCommand(chr(c1)+chr(c2)) + + replies, are_valid = self.validate(commands) + + for i, is_valid in enumerate(are_valid): + if is_valid: + print(commands[i], replies[i]) + + def validate(self, commands): + are_valid = [] + replies = [] + one_valid = False + for command in commands: + try: + self.port.flush() + self.port.writeData(command) + reply = self.readStringFromPMT() + if reply != "BC": + are_valid.append(True) + else: + are_valid.append(False) + replies.append(reply) + except Exception as err: + are_valid.append(False) + replies.append(None) + + return replies, are_valid + + # self.port.writeData(b"\r") + + def test08start_counting(self): + """ + Here, I printed the read data for a while and noticed + an indexed value going up by one. And some weird garbage values after. + """ + self.port.defaultTimeout = 5000 + + self.port.writeData(b"C") + + count = 0 + while count < 4: + try: + replyData = self.port.readData(64) + print(replyData) + count += 1 + except Exception as err: + print(err) + count += 1 + pass + + + def test08GetIntegrationTime(self): + """ + Here, I noticed the 'I' command appeared valid in my long list of attempts. + I printed the reply data for a while and noticed + an value corresponding to the letter 'I', a few zeros. And some weird garbage values after. + + Eventually, I figured it out: the first 32 bits are the command letter ASCII + code in a lttle endian 32-bit integer, and then the integration time also as a 32 bit + integer. But then, the nuber was 100 000, not 1000 as expected. I assumed + this meant my model was in 10µs units. + + So 'I' replied with 'I',0x00,0x00,0x00,0xa0,0x86,0x01,0x00 + So I decided to try to send the same command back on the next test. + """ + self.port.writeData(b"I") + replyData = self.port.readData(64) + replyData = replyData[0:8] + print(replyData) + index, photonCount = unpack("