在将旧电源改造为可通过智能手机控制中,我们通过赋予一台旧电源物联网(IoT)功能,为其注入了新生命。在本文中,我们将探讨一种更加简洁的方法,将物联网功能引入您所有的实验室仪器(特别是那些使用VISA控制的仪器)。我们将利用生成式AI来完成大部分繁重的工作,而不是从头开始构建。通过本教程,您应该能够将这些概念应用于构建所有实验室仪器的网络驱动程序,并加速您的整体开发进程。
在本文中,我们将重点介绍搭建作为仪器本身与互联网之间中介的网络服务。以下是仪器与互联网(即通过网站访问)之间端到端连接的示意图。
图1:仪器与互联网之间的端到端通信
在此之前,重要的是要明确我们不完全需要自己编写所有仪器驱动程序。我们可以利用制造商在线完成的工作,或者利用开源仓库。为了简洁起见,我将利用我在线找到的资源,但使用生成式AI来构建一个我满意的框架。
我使用的是Rigol的DP832电源和DL3021电子负载。在GitHub上快速搜索后,我找到了DP832电源的Python库,其中包含了开始所需的所有必要SCPI命令。另一种方法是,从DP832手册中取出一份SCPI命令列表,交给一个大型语言模型(LLM),例如ChatGPT或Gemini,让它为我生成函数。我有点挑剔,所以我将定义我自己的PyVISA设置过程,然后利用已经完成的大部分工作。这是我的通用PyVISA设置过程(封装在一个单独的类中):
class CommBase:
USBConnType = Literal["USBTMC", "VISA", "Socket"]
def __init__(self, usb_conn_type: USBConnType, vid: int = None, pid: int = None, visa_resource_prefix: str = None):
self.visa_resource_prefix = visa_resource_prefix
self.usb_conn_type = usb_conn_type
if usb_conn_type == "USBTMC":
self.configure_usbtmc(vid, pid)
elif usb_conn_type == "VISA":
``` self.configure_visa(vid, pid) ``` ``` elif usb_conn_type == "Socket": ``` ``` pass ``` ``` else: ``` ``` raise ValueError(f"Invalid USB connection type: {usb_conn_type}. Valid types are {self.VALID_USB_CONN_TYPES}") ``` ``` ``` ``` def configure_usbtmc(self, vid: int, pid: int): ``` ``` self.inst = usbtmc.Instrument(vid, pid) ``` ``` ``` ``` def configure_visa(self, vid: int, pid: int): ``` ``` self.rm = pyvisa.ResourceManager() ``` ``` instrument_list = self.rm.list_resources() ``` ``` visa_address = self.find_visa_resource(vid, pid, instrument_list, prefix=self.visa_resource_prefix) ``` ``` if visa_address is not None: ``` ``` self.inst = self.rm.open_resource(visa_address, read_termination="\n") ``` ``` else: ``` ``` raise IOError(f'No VISA devices found using vid "{vid}" and pid "{pid}" but the following VISA devices have been found: {instrument_list}') ``` ``` ``` ``` @staticmethod ``` ``` def find_visa_resource(vid: int, pid: int, resource_strings: list, prefix: str) -> str: ``` ``` hex_vid, hex_pid = f"0x{vid:X}", f"0x{pid:X}" ``` ``` dec_vid, dec_pid = str(vid), str(pid) ``` ``` for resource in resource_strings: ``` ``` parts = resource.split("::") ``` ``` if len(parts) >= 4: ``` ``` serial_and_more = parts[3] ``` ``` if (any(x in resource for x in (hex_vid, dec_vid)) and any(x in resource for x in (hex_pid, dec_pid)) and serial_and_more.startswith(prefix)): ``` ``` return resource ``` ``` return None ``` ``` ``` ``` def query_device(self, command: str) -> str: ``` ``` if self.usb_conn_type == "USBTMC": ``` ``` return self.inst.ask(command) ``` ``` elif self.usb_conn_type == "VISA": ``` ``` return self.inst.query(command) ``` ``` else: ``` ``` raise NotImplementedError(f"Query method for {self.usb_conn_type} not found.") ``` ``` ``` ``` def write_device(self, command: str): ``` ``` self.inst.write(command) ``` ``` ``` ``` def close(self): ``` ``` self.inst.close() ``` ``` if self.usb_conn_type == "VISA": ``` ``` self.rm.close() ``` ``` ``` ``` def id(self) -> dict: ``` ``` id_str = self.query_device("*IDN?").strip().split(",") ``` ``` return { ``` ``` "manufacturer": id_str[0], ``` ``` "model": id_str[1], ``` ``` "serial_number": id_str[2], ``` ``` "version": id_str[3], ``` ``` } ``` 我已经移除了所有的注释、额外的空格,甚至一些设置序列(如日志记录)以简化内容。正如你所见,我有一些通用函数以及对 PyVISA 和 USBTMC 的支持。你在网上找到的大多数基于 SCPI 的 Python 库不会有这种基础功能。没关系,因为我可以将这个类扩展到我的新类中: ``` from base.CommBase import CommBase ``` ``` class DP(CommBase): ``` ``` def channel_check(self, channel): ``` ``` assert NotImplementedError ``` ``` def get_output_mode(self, channel: int) -> str: ``` ``` self.channel_check(channel) ``` ``` return self.query_device(f":OUTP:MODE? CH{channel}").strip() ``` ``` # … 代码已经被移除以简化 ``` ``` def measure_current(self, channel): ``` ``` self.channel_check(channel) ``` ``` meas = self.query_device(f":MEAS:CURR? CH{channel}").strip() ``` ``` return float(meas) ``` ``` def measure_voltage(self, channel): ``` ``` self.channel_check(channel) ``` ``` meas = self.query_device(f":MEAS? CH{channel}").strip() ``` ``` return float(meas) ``` ``` def measure_all(self, channel): ``` ``` self.channel_check(channel) ``` ``` meas = self.query_device(f":MEAS:ALL? CH{channel}").strip().split(",") ``` ``` return { ``` ``` "voltage": float(meas[0]), ``` ``` "current": float(meas[1]), ``` ```plaintext
"power": float(meas[2]),
}
class DP712(DP):
def channel_check(self, channel):
assert channel in [1, ""], f"输出通道 {channel} 不受支持"
class DP821(DP):
def channel_check(self, channel):
assert channel in [1, 2, ""], f"输出通道 {channel} 不受支持"
class DP832(DP):
def channel_check(self, channel):
assert channel in [1, 2, 3, ""], f"输出通道 {channel} 不受支持"
我在网上找了几个不同的例子,并将它们传入了ChatGPT。我提示它:
利用生成式AI进行这项任务,将多小时的工作缩短到了60秒。我也能够非常快速地通过运行ChatGPT为我编写的自动生成的测试来验证我在线上找到的驱动程序。例如,我很快发现,用于设置电子负载DL3021操作模式的一个SCPI命令实际上是不正确的。实时观察测试运行时,我注意到我的仪器上的模式没有改变。快速查阅SCPI手册后,我能够对其进行更正。
此时,我们已经为控制我们的仪器建立了一个良好的Python库基础。现在的目标是在仪器库前面放置一个web服务,使我们能够通过网络控制它们。假设我对web框架一无所知,我可以简单地请求ChatGPT(或任何LLM)为我完成整个功能。这是我用来开始的提示:
我需要创建一个web服务,通过基本的URL(通过web浏览器)控制我的仪器。我对web服务或框架不太了解,但我熟悉Python。编写完整的Python代码来完成这项任务。
这是控制我的仪器的类:
<pre><code>```
{上面的代码}
```
</code></pre>
以及(部分回应):
```
图 2:ChatGPT 提示的响应
我最喜欢的关于高级(付费)LLMs的部分是,它们真的会为你分解任务,教育你,并提供一个相当不错的第一次迭代解决方案。在 Gemini 与 ChatGPT:谁写的代码更好?中,我将这两个LLMs相互对比,(剧透警告)发现谷歌的 Gemini 版本实际上相当不错(如果不是与 ChatGPT 4 平起平坐的话)。使用这两个LLMs中的任何一个都会产生类似上面所示的结果。
如果我想把这个放入一个类或重新格式化它,我可以简单地回应聊天并提出我的请求。想要你的代码加注释?没问题,只需问!
提示:为每个函数添加 docstring 风格的注释块。重写整个类,以便我可以将其复制并粘贴回我的代码编辑器。
图 3:ChatGPT 提示的下一个响应
现在我们已经让生成式AI创建了Python模块,我们可以要求它为我们编写测试,或者展示如何手动测试:
图 4:ChatGPT 提供的手动测试 URL
图 5:ChatGPT 编写的自动化测试
在运行(或调整后运行)这些测试之后,我们应该能够验证我们新的Python模块,该模块在网页和我们的仪器之间进行接口。
在这篇文章中,我们整合了一个Python模块,该模块利用网络上现有的代码和生成式AI来控制我们的仪器。在我们的LLM完成了该模块之后,我们引入了一个新的模块,作为互联网和仪器本身(通过网络服务)之间的中介。我们还快速查看了生成式AI如何引导我们完成整个过程,包括手动或自动测试。这次练习最棒的部分是,如果操作正确,应该只需要很少的时间就能完成。第一次可能需要一些额外的努力,但重复这个过程用于许多仪器应该是轻而易举的(并且开发时间非常少)。很明显,生成式AI已经彻底改变了我们所有人的景观,这篇文章确实展示了这一点。希望你将受到启发,利用生成式AI创建下一套仪器库,并将它们贡献给社区的其他成员。
此项目中使用的所有源代码可以在以下地址找到:https://gitlab.com/ai-examples/instrument-controllables。