Skip to main content

跨平台(macOS、Windows、Linux 等)库,用于控制主要用于科学应用的各种硬件设备。

项目描述

PyHardwareLibrary

一个简单的面向设备的库,用于控制实验室中的硬件设备。

您可能会在这里做以下两件事之一:

  1. 您想使用设备(例如,Ocean Insight 光谱仪),获取数据并保存。
  2. 您想编写驱动程序以使新设备在您的计算机上工作。

如果这适用于您,请继续阅读。

了解更多

如果您有兴趣了解更多关于:

基本上按照这个顺序阅读以上所有内容将是一个很好的计划。

这个硬件库的目的是什么?

我们经常需要控制实验室中的设备(线性平台、光谱仪、相机、百叶窗等......)。许多公司提供的驱动程序是一个好的开始,但将设备集成到定制软件中有时会变得很困难。创建这个 Python 模块是为了促进驱动程序的开发促进应用程序的创建,并提供最小但有用的应用程序。用于实验室经常使用的硬件。它源于一个我个人维护了近 10 年的(私人)项目,其中驱动程序是用 Objective-C 编写的,包括对我实验室中使用的 30 多种不同设备的支持。然而,Python 更常在学校教授,并且基本上支持所有平台,因此我开始这个项目是为了 1)教如何开发简单的驱动程序,2)向学生传授良好的编程实践,3)让硬件工作对于我自己的实验室,无论使用何种平台(我们使用 macOS 和 Windows),4) 获得帮助以缩短开发周期以支持更多设备。

为什么是 Python?Python 是面向对象的(必不可少的)并且提供了合理的性能。Python 还具有成为非常优秀的团队成员的品质:在任何平台上将 Python 与任何东西集成相当容易,并且社区非常活跃。来自公司的众多 Python SDK、PyPi.org 上的数千个模块以及所有供应商(Microsoft、Apple 和 Linux)的支持是显而易见的。Python 也不是一门死语言:我很高兴看到这门语言多年来随着新的语言特性和新的标准模块而发展。

开始使用设备

要使用此模块,您需要通过从 GitHub下载它然后输入:来安装它python setup.py install

目前,唯一支持的有用设备是 Ocean Insight 光谱仪USB2000USB4000. 如果你只想使用它,那么下面的两行脚本就可以了:

from hardwarelibrary.spectrometers import OISpectrometer

OISpectrometer.displayAny()

将选择连接的第一个支持的光谱仪,并出现一个显示光谱的窗口。

如果您想做更多的事情,例如将其集成到您正在编写的其他软件中,此时最好的选择是键入help(someClass)以从代码中获取帮助:

>>> from hardwarelibrary.spectrometers import OISpectrometer
>>> help(OISpectrometer)

Help on class OISpectrometer in module hardwarelibrary.spectrometers.oceaninsight:

class OISpectrometer(builtins.object)
 |  OISpectrometer(idProduct, model, serialNumber=None)
 |  
 |  An Ocean insight (Ocean Optics) spectrometer.  This allows complete access
 |  to the hardware with simple functions to get the spectrum, or modify the
 |  integration time. It is the base class for all Ocean Insight Spectrometers,
 |  but you will not instantiate this directly: use USB2000() or USB4000(),
 |  or simply: OISpectrometer.any() to get any spectrometer.
 |  
 |  Access to the device is done with pyusb and does not require any
 |  additional information. The USB-specific attributes of the spectrometers are
 |  available,  but are not needed for standard usage.  If you need to
 |  implement additional functions and communicate with the device (not all
 |  capabilities are currently coded), then you could implement them in a
 |  separate function.
 |  [...]
 |  getCalibration(self)
 |      Get the hardcoded calibration from the spectrometer.  It is a
 |      3rd-order polynomial. Currently, no nonlinearities are considered.
 |  
 |  getIntegrationTime(self)
 |      Get the integration time in as a float value in milliseconds
 |      cls.timeScale is 1 for ms and 1000 if it is stored in µs
 |  
 |  getParameter(self, index)
 |      Get any of the 20 parameters hardcoded into the spectrometer.
 |      
 |      Parameters
 |      ----------
 |      
 |      index: int
 |          0  Serial Number
 |          1  0th order Wavelength Calibration Coefficient 
 |          2  1st order Wavelength Calibration Coefficient
[...]

当然,在不久的将来会支持其他设备。如本文其他部分所述,本PyHardwareLibrary项目源自一个HardwareLibrary支持许多不同设备的私人项目,例如 Sutter Instruments 载物台、许多 Thorlabs 设备(载物台、快门、旋转载物台、翻转镜)、Spectra Physics Lasers、Cobolt Lasers、Olympus显微镜、GenTech-EO 功率计、LabJack、Zaber、Marzhauer、Prior、Newport、Intellidrive,因此在将它们移植到PyHardwareLibrary.

开始为新设备编码

但也许您的兴趣不仅在于使用这些设备,还在于学习如何编写代码来控制它们。您应该在此处找到有关如何进行的大量文档。

您会发现一个简单的脚本cobolt.py,用于更改 Cobolt 激光器的功率。共有三个版本,您应该阅读所有三个示例:

  1. 1-simple:一个非常简单的实现,按顺序排列简单的命令
  2. 2-classCoboltLaser:部分封装细节并公开一些功能的类实现:setPower()power()
  3. 3-class+debugPort:具有模拟真实设备的调试端口的类实现
  4. 代码的主要部分有一个CoboltDevice支持turnOn() turnOff(),setPower()power()

这只是一个非常简单的激光示例,可能很少有人可以使用,但应该给出一个大致的概念。

战略

如何支持新设备?什么是最好的策略?

  1. 获取手册。查找连接信息(通常在文本中搜索ASCII或)。serial您将找到诸如“波特率、停止位、硬件握手”和最重要的“ASCII 或二进制命令”之类的信息。这就是你需要的。

    1. 如果您无法从网站获取手册,请联系公司。如上所述,许多人会很乐意为您提供帮助:他们通常想销售设备或满足购买设备的客户。
  2. 以一种或另一种方式连接到设备。

    1. 如有必要,可能需要安装驱动程序来序列化设备(使其显示为串行端口)。在这种情况下,您将SerialPort在安装该驱动程序后使用该类。
      1. 并非所有设备都可以显示为“串行端口”。简单的设备(例如翻译阶段)很好,因为它们只是读取命令(“MOVE”)并回复(“OK”)。但是,其他设备(相机,光谱仪)响应命令并传输数据,有时需要很多并且需要许多通信线路。USB 标准提供了这一点(带有端点),但不是旧式串行端口,它本质上只是单通道上的双向通信。
      2. 此外,要使设备显示为串行端口,制造商需要在设备的 USB 描述符中提供一定量的信息。如果他们不这样做,你就不走运了。
    2. 如果标准串行端口不可用,则可能需要使用libusb和进行直接 USB 访问PyUSB。这是最优雅的解决方案,但需要一些 USB 知识。 广泛PyHardwareLibrary利用,简化沟通。PyUSBUSBPort
    3. 弄清楚(最好通过测试,见下一点)如何连接SerialPortor USBPort,这两个派生类都来自CommunicationPort
  3. 识别命令并编写非常简单的测试SerialPort 来确认连接和验证命令语法(有关更多详细信息,请参阅下面的其他部分):

        class TestCoboltSerialPort(unittest.TestCase):
    				def testLaserOn(self):
    					self.port = SerialPort("COM5") # Are settings right? Baud rate, stop bits, etc...
    					self.port.writeStringExpectMatchingString('l1\r',replyPattern='OK')
    
  4. 创建一个DebugSerialPort, 基于CommunicationPort 复制的行为SerialPort()来模仿真实的串行端口。参见CoboltDebugSerial示例。

  5. 完成将测试真实端口和调试端口的串行测试。两者的行为必须相同。

  6. 这部分还没有完全实现:开始将复杂的串行通信封装在一个PhysicalDevice-derivative 中(例如 , LaserSourceDevice,LinearMotionDevice等等...)。例如,查看CoboltDevice哪个派生自LaserSourceDevice. 有关 的策略的更多详细信息PhysicalDevice,请参阅部分:PhysicalDevice实施。

  7. 编写一系列设备测试。例如,请参阅testCoboltDevice

  8. 在您的设备中,您必须能够使用您的DebugSerialPort. 这样,testCoboltDevice既可以在真实设备上运行,也可以在调试设备上运行。

  9. 当所有测试都通过 ( Port, DebugPort, Device, DebugDevice) 时,您就完成了

测试串口

在测试串行端口时,我们想要测试到给定设备的真实连接和行为类似的模拟实现(例如)。 DebugPort因此,我们希望在每个端口上运行一系列测试。在两个不同实例上运行一系列测试的最佳策略如下:

  1. 创建一个BaseTestCases不继承自的类,并unittest.TestCase使用一个继承自的内部类unittest.TestCases

    class BaseTestCases:
    
       class TestCoboltSerialPort(unittest.TestCase):
          self.port = None
    
          ...
    
  2. 声明对测试有用的变量(self.port例如)。

  3. 不要定义setUp() 或tearDown()

  4. 使用您需要的所有测试方法填充类,名称以 开头test*

    class BaseTestCases:
    
     class TestCoboltSerialPort(unittest.TestCase):
         port = None
    
         def testCreate(self):
             self.assertIsNotNone(self.port)
    
         def testCantReopen(self):
             self.assertTrue(self.port.isOpen)
             with self.assertRaises(Exception) as context:
                 self.port.open()
         ...
    
  5. 在同一个文件中,定义两个继承自BaseTestCaseswith 的测试子类setUp()tearDown()特定于真实端口或调试端口的方法。因此,它们将继承父类BaseTestCases的所有方法并拥有所有测试方法。

    class TestDebugCoboltSerialPort(BaseTestCases.TestCoboltSerialPort):
        def setUp(self):
           self.port = CommunicationPort(port=CoboltDebugSerial())
           self.assertIsNotNone(self.port)
           self.port.open()
    
        def tearDown(self):
           self.port.close()
    
    class TestRealCoboltSerialPort(BaseTestCases.TestCoboltSerialPort):
       def setUp(self):
           try:
                 self.port = CommunicationPort(port="COM5")
                 self.port.open()
           except:
                 raise unittest.SkipTest("No cobolt serial port at COM5")
       def tearDown(self):
           self.port.close()
    
  6. 如果您有特定于给定端口的测试方法,则在特定类中定义它们。

  7. 在文件末尾添加以下内容:

    if __name__ == '__main__':
        unittest.main()
    
  8. 通过使用 运行此文件中的测试python testCoboltSerial.py,unittest 框架将自动运行来自TestDebugCoboltSerialPort和的所有测试TestRealCoboltSerialPort。当然,两者都应该通过所有测试才能成功。

  9. 此策略可用于测试 aDevice及其DebugDevice对应物。

设计目标

这部分还没有在这个库中完全实现,而是在一个单独的项目中实现。

通过串口与设备通信是第一步。然而,大多数时候,我们关心的是我们想用设备完成的一些任务(打开并使用激光,从光谱仪获取光谱等......)。因此,在弄清楚命令是什么以及设备如何响应之后,将所有这些命令“包装”或封装在一个类(或对象)中很重要,该类(或对象)向最终用户表示设备并使其易于无需了解详细信息即可使用。PyHardwareLibrary使用一个名为的基类PhysicalDevice

真正的物理设备处理起来并不简单:任何时候都可能发生错误(因为设备本身),因为用户没有连接它或没有打开它,因为设备处于不规则状态(例如,它例如到达行程范围的终点)。因此,优雅地处理错误变得很重要,尤其是稳健地处理错误。

本库使用的策略如下:

  1. 设备的许多属性是通用的:具有 USB 供应商 ID、产品 ID、序列号等……这包含在一个称为父类的父类中,该类PhysicalDevice是所有设备的父类。
  2. 很多方法也很常见:必须初始化所有设备、关闭设备等……这些方法在父类中定义,但调用派生类的设备特定方法。例如,initializeDevice() 是否进行了一些内务处理(设备是否已经初始化?底层初始化是否成功?)以及doInitializeDevice 必须由派生类实现的调用。如果初始化失败,它必须引发错误。该类必须确认设备至少响应一个内部命令以确认它确实是预期的设备。
  3. 对于特定的设备类(例如LaserSourceDevice),使用特定的方法来隐藏实现的细节:LaserSourceDevice.turnOn()LaserSourceDevice.power()LaserSourceDevice.setPower()等……这些方法调用do派生类(例如 )中具有相似名称(前缀为 )doTurnOn()的特定于设备的方法
  4. 以 开头的方法do 通过串口与设备通信。他们必须将请求的结果存储到一个实例变量中(以缓存该值并避免每次需要该值时都返回串行端口)。例如,一个实例self.power存储从doGetPower().
  5. do方法永远不会被用户调用。用户调用turnOn()方法而不是doTurnOn()方法。如果 Python 作为一门语言允许,这些do方法将是隐藏的和私有的,但看起来不可能:唯一的约定是使用_do,但它只是一个约定,函数仍然可以被调用。

动机

我还必须发泄我的挫败感,即制造商的最终用户软件通常设计得很糟糕、有错误和/或只是使用起来令人沮丧,但大多数时候,以上所有情况都是如此。我什至看过一些公司的示例代码,它们甚至根本不编译。其他人只会支持Windows 7,甚至在2021年还板着脸说这完全正常。最重要的是,许多公司会(错误地)声称他们的硬件无法在我选择的平台 macOS 上运行。这通常是因为剪切懒惰或直接无能:只要它可以连接到计算机,它就可以支持。对于 USB 设备,这通常是微不足道的编写一个“驱动程序”来支持具有适当文档的设备,我已经多次这样做了。经验法则是,拥有良好软件的公司说,在 Windows 上,通常在许多平台上都有良好的软件,因为他们显然了解如何编程并理解编写跨平台代码的简单性,如果您将其作为设计要求。另一方面,我发现缺乏对 Windows 以外的平台的支持通常会导致 Windows 上的软件相当糟糕:这些公司往往是硬件公司,他们认为软件只是次要的,可能会将其外包出去。向 ActiveSilicon、Sutter Instruments、Hamamatsu、Ocean Insight(感谢他们的文档,但肯定不是他们的软件)和 Thorlabs 对开发人员友好表示感谢:他们根据要求提供所有必要的信息,对科学家有很大帮助。另一方面,这是一个中指