From 47149df01a8ae00a20f76231a56adfd490e28b5a Mon Sep 17 00:00:00 2001 From: Soloman J Date: Wed, 25 Mar 2026 09:03:42 -0600 Subject: [PATCH 1/2] Initial U12 Windows low level function support. Needs testing and documentation updates. --- src/u12.py | 77 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/src/u12.py b/src/u12.py index 3afe4ed..fc9e1d8 100644 --- a/src/u12.py +++ b/src/u12.py @@ -432,7 +432,7 @@ def __init__(self, id = -1, serialNumber = None, debug = False): self.pwmBVoltage = 0 self.IO3toIO0DirAndStates = BitField(rawByte = 240) - self.open(id, serialNumber) + self.open(id, serialNumber) def _debugprint(self, msg): """Conditionally output msg. @@ -457,10 +457,33 @@ def open(self, id = -1, serialNumber = None): a handle. On Windows, this method does nothing. On Mac OS X and Linux, this method acquires a device handle and saves it to the U12 object. """ + self._debugprint("open called") if _os_name == "nt": - pass + ecode = ctypes.c_long(0) + vID = ctypes.c_uint(3285) #0x0CD5, LabJack vendor ID + pID = ctypes.c_uint(1) # U12 product ID + idnum = ctypes.c_long(id) + if serialNumber is None: + serialNumber = 0 + serialnum = ctypes.c_long(serialNumber) + numCals = 20 + calData =(ctypes.c_long*numCals)(0) + self.handle = staticLib.OpenLabJack( + ctypes.byref(ecode), + vID, + pID, + ctypes.byref(idnum), + ctypes.byref(serialnum), + ctypes.byref(calData) + ) + if self.handle is None: + raise U12Exception( + "Couldn't find a U12 with matching open parameters." + ) + if ecode.value != 0: raise U12Exception(ecode) + self.id = idnum.value + self.serialNumber = serialnum.value else: - self._debugprint("open called") devType = ctypes.c_ulong(1) openDev = staticLib.LJUSB_OpenDevice openDev.restype = ctypes.c_void_p @@ -532,26 +555,35 @@ def open(self, id = -1, serialNumber = None): raise Exception("Invalid combination of parameters.") - if not self._autoCloseSetup: - # Only need to register auto-close once per device. - atexit.register(self.close) - self._autoCloseSetup = True + if not self._autoCloseSetup: + # Only need to register auto-close once per device. + atexit.register(self.close) + self._autoCloseSetup = True def close(self): if _os_name == "nt": - pass + staticLib.CloseAll(self.handle) else: staticLib.LJUSB_CloseDevice(self.handle) - self.handle = None + self.handle = None def write(self, writeBuffer): - if _os_name == "nt": - pass - else: - if self.handle is None: + if self.handle is None: raise U12Exception("The U12's handle is None. Please open a U12 with open().") - self._debugprint("Writing: " + hexWithoutQuotes(writeBuffer)) + self._debugprint("Writing: " + hexWithoutQuotes(writeBuffer)) + + if _os_name == "nt": + writeBuffer.insert(0,0) # Windows requires an extra byte at the start + newA = (ctypes.c_ubyte*len(writeBuffer))(0) + for i in range(len(writeBuffer)): + newA[i] = ctypes.c_ubyte(writeBuffer[i]) + + ecode = staticLib.WriteLabJack(self.handle, ctypes.byref(newA)) + if ecode != 0: raise U12Exception(ecode) + writeBuffer.pop(0) # Remove our extra byte from the start + + else: newA = (ctypes.c_ubyte*len(writeBuffer))(0) for i in range(len(writeBuffer)): newA[i] = ctypes.c_ubyte(writeBuffer[i]) @@ -561,20 +593,27 @@ def write(self, writeBuffer): if writeBytes != len(writeBuffer): raise U12Exception("Could only write %s of %s bytes." % (writeBytes, len(writeBuffer) ) ) - return writeBuffer + return writeBuffer def read(self, numBytes=8, timeout=1000): + if self.handle is None: + raise U12Exception("The U12's handle is None. Please open a U12 with open().") if _os_name == "nt": - pass + numBytes+=1 # Windows requires reading an extra byte + newA = (ctypes.c_ubyte*numBytes)() + ecode = staticLib.ReadLabJack(self.handle, timeout, 0, ctypes.byref(newA)) + if ecode != 0: raise U12Exception(ecode) + # Return a list of integers in command-response mode + # The first byte read must be tossed. + result = [(newA[i] & 0xff) for i in range(1, numBytes)] + self._debugprint("Received: " + hexWithoutQuotes(result)) else: - if self.handle is None: - raise U12Exception("The U12's handle is None. Please open a U12 with open().") newA = (ctypes.c_ubyte*numBytes)() readBytes = staticLib.LJUSB_ReadTO(self.handle, ctypes.byref(newA), numBytes, timeout) # Return a list of integers in command-response mode result = [(newA[i] & 0xff) for i in range(readBytes)] self._debugprint("Received: " + hexWithoutQuotes(result)) - return result + return result # Low-level helpers def rawReadSerial(self): From 504d6e0583c609e08cc1b5a241079ec0c97378f5 Mon Sep 17 00:00:00 2001 From: Soloman J Date: Wed, 25 Mar 2026 14:59:02 -0600 Subject: [PATCH 2/2] Update U12 raw function comments, fix some formatting. --- src/u12.py | 85 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/src/u12.py b/src/u12.py index fc9e1d8..ef77d89 100644 --- a/src/u12.py +++ b/src/u12.py @@ -5,18 +5,17 @@ low-level. Most of the UW functions are exposed as functions of the U12 class. With - the exception of the "e" functions, UW functions are Windows only. The "e" - functions will work with both the UW and the Exodriver. Therefore, people - wishing to write cross-platform code should restrict themselves to using - only the "e" functions. The UW functions are described in Section 4 of the - U12 User's Guide: + the exception of the "e" functions, UW functions are Windows only. The + "e" functions will work with both the UW and the Exodriver, as will the + "raw" functions. Therefore, people who want to write cross-platform code + should restrict themselves to using the "e" or "raw" functions. The UW + functions are described in Section 4 of the U12 User's Guide: http://labjack.com/support/u12/users-guide/4 All low-level functions of the U12 class begin with the word - raw. For example, the low-level function Counter can be called with - U12.rawCounter(). Currently, low-level functions are limited to the - Exodriver (Linux and Mac OS X). You can find descriptions of the low-level + "raw". For example, the low-level function Counter can be called with + U12.rawCounter(). You can find descriptions of the low-level functions in Section 5 of the U12 User's Guide: http://labjack.com/support/u12/users-guide/5 @@ -425,6 +424,9 @@ def __init__(self, id = -1, serialNumber = None, debug = False): self.handle = None self.debug = debug self._autoCloseSetup = False + # USB vendor and product ID + self.vendorID = 0x0CD5 + self.productID = 1 if _os_name != "nt": # Save some variables to save state. @@ -436,12 +438,12 @@ def __init__(self, id = -1, serialNumber = None, debug = False): def _debugprint(self, msg): """Conditionally output msg. - + If self.debug is a logging.Logger object, send the msg to it with DEBUG priority. Otherwise, if self.debug is any truthy, just print it to stdout. """ - + if self.debug: if isinstance(self.debug, logging.Logger): self.debug.debug(msg) @@ -453,15 +455,16 @@ def open(self, id = -1, serialNumber = None): Opens the U12. The Windows UW driver opens the device every time a function is called. - The Exodriver, however, works like the UD family of devices and returns - a handle. On Windows, this method does nothing. On Mac OS X and Linux, - this method acquires a device handle and saves it to the U12 object. + The Exodriver and "raw" functions work like the UD family of devices + and returns a handle. This method acquires a device handle and saves it + to the U12 object for use with the "raw" functions (and "e" functions + when using macOS or Linux). """ self._debugprint("open called") if _os_name == "nt": ecode = ctypes.c_long(0) - vID = ctypes.c_uint(3285) #0x0CD5, LabJack vendor ID - pID = ctypes.c_uint(1) # U12 product ID + vID = ctypes.c_uint(self.vendorID) # LabJack USB vendor ID + pID = ctypes.c_uint(self.productID) # U12 product ID idnum = ctypes.c_long(id) if serialNumber is None: serialNumber = 0 @@ -477,10 +480,10 @@ def open(self, id = -1, serialNumber = None): ctypes.byref(calData) ) if self.handle is None: - raise U12Exception( - "Couldn't find a U12 with matching open parameters." - ) - if ecode.value != 0: raise U12Exception(ecode) + raise U12Exception( + "Couldn't find a U12 with matching open parameters." + ) + if ecode.value != 0: raise U12Exception(ecode.value) self.id = idnum.value self.serialNumber = serialnum.value else: @@ -569,50 +572,66 @@ def close(self): def write(self, writeBuffer): if self.handle is None: - raise U12Exception("The U12's handle is None. Please open a U12 with open().") - + raise U12Exception( + "The U12's handle is None. Please open a U12 with open()." + ) self._debugprint("Writing: " + hexWithoutQuotes(writeBuffer)) if _os_name == "nt": - writeBuffer.insert(0,0) # Windows requires an extra byte at the start + # Windows requires an extra byte at the start + writeBuffer.insert(0,0) newA = (ctypes.c_ubyte*len(writeBuffer))(0) for i in range(len(writeBuffer)): newA[i] = ctypes.c_ubyte(writeBuffer[i]) - ecode = staticLib.WriteLabJack(self.handle, ctypes.byref(newA)) if ecode != 0: raise U12Exception(ecode) writeBuffer.pop(0) # Remove our extra byte from the start - else: newA = (ctypes.c_ubyte*len(writeBuffer))(0) for i in range(len(writeBuffer)): newA[i] = ctypes.c_ubyte(writeBuffer[i]) - - writeBytes = staticLib.LJUSB_Write(self.handle, ctypes.byref(newA), len(writeBuffer)) - + writeBytes = staticLib.LJUSB_Write( + self.handle, + ctypes.byref(newA), + len(writeBuffer) + ) if writeBytes != len(writeBuffer): - raise U12Exception("Could only write %s of %s bytes." % (writeBytes, len(writeBuffer) ) ) + raise U12Exception( + "Could only write %s of %s bytes." % \ + (writeBytes, len(writeBuffer)) + ) return writeBuffer def read(self, numBytes=8, timeout=1000): if self.handle is None: - raise U12Exception("The U12's handle is None. Please open a U12 with open().") + raise U12Exception( + "The U12's handle is None. Please open a U12 with open()." + ) if _os_name == "nt": numBytes+=1 # Windows requires reading an extra byte newA = (ctypes.c_ubyte*numBytes)() - ecode = staticLib.ReadLabJack(self.handle, timeout, 0, ctypes.byref(newA)) + ecode = staticLib.ReadLabJack( + self.handle, + timeout, + 0, + ctypes.byref(newA) + ) if ecode != 0: raise U12Exception(ecode) # Return a list of integers in command-response mode # The first byte read must be tossed. result = [(newA[i] & 0xff) for i in range(1, numBytes)] - self._debugprint("Received: " + hexWithoutQuotes(result)) else: newA = (ctypes.c_ubyte*numBytes)() - readBytes = staticLib.LJUSB_ReadTO(self.handle, ctypes.byref(newA), numBytes, timeout) + readBytes = staticLib.LJUSB_ReadTO( + self.handle, + ctypes.byref(newA), + numBytes, + timeout + ) # Return a list of integers in command-response mode result = [(newA[i] & 0xff) for i in range(readBytes)] - self._debugprint("Received: " + hexWithoutQuotes(result)) + self._debugprint("Received: " + hexWithoutQuotes(result)) return result # Low-level helpers