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 @@
-
+
+
+
+
+
@@ -70,7 +74,7 @@
-
+
@@ -81,7 +85,7 @@
-
+
@@ -126,7 +130,7 @@
-
+
@@ -137,11 +141,11 @@
-
+
-
+
@@ -152,11 +156,11 @@
-
+
-
+
@@ -167,11 +171,11 @@
-
+
-
+
@@ -182,41 +186,41 @@
-
-
+
+
-
+
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -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("