En Adaptando tu antigua fuente de alimentación para ser controlada por smartphone le dimos nueva vida a una vieja fuente de alimentación otorgándole capacidades IoT. En este artículo vamos a ver una manera más eficiente de brindar capacidades IoT a todos tus instrumentos de laboratorio (específicamente aquellos controlados usando VISA). En lugar de construirlo desde cero, vamos a aprovechar la Inteligencia Artificial Generativa para realizar la mayor parte del trabajo pesado. Después de seguir este tutorial, deberías ser capaz de tomar estos conceptos para construir controladores web habilitados para todos tus instrumentos de laboratorio y también acelerar tu desarrollo general.
En este artículo nos vamos a centrar en armar el servicio web que actúa como intermediario entre el instrumento en sí y el Internet. La siguiente es una ilustración de la conexión de extremo a extremo entre el instrumento y el internet (es decir, acceso a través de sitio web).
Figura 1: Comunicación de extremo a extremo entre el instrumento y el Internet
Antes de hacer eso, es importante establecer que no es completamente necesario escribir todos los controladores de instrumentos nosotros mismos. Podemos aprovechar lo que ya está hecho en línea por los fabricantes o podemos utilizar repositorios de código abierto. Por razones de brevedad, voy a aprovechar lo que ya encontré en línea pero usar inteligencia artificial generativa para armar un marco de trabajo con el que esté satisfecho.
Estoy usando la fuente de alimentación DP832 y la carga electrónica DL3021 de Rigol. Después de una rápida búsqueda en GitHub, encontré bibliotecas de Python para la fuente de alimentación DP832 que contienen todos los comandos SCPI necesarios para comenzar. Un enfoque alternativo sería tomar una lista de comandos SCPI del manual del DP832, entregársela a un modelo de lenguaje grande (LLM), por ejemplo, ChatGPT o Gemini, y hacer que genere las funciones para mí. Soy un poco exigente, así que voy a definir mi propio proceso de configuración de PyVISA y luego aprovechar la mayoría de lo que ya se ha hecho. Aquí está mi proceso de configuración de PyVISA genérico (encapsulado en una clase separada):
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"Tipo de conexión USB inválido: {usb_conn_type}. Los tipos válidos son {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 se encontraron dispositivos VISA usando vid "{vid}" y pid "{pid}" pero se encontraron los siguientes dispositivos VISA: {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"El método de consulta para {self.usb_conn_type} no se encontró.")
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],
}
He eliminado todos los comentarios, espacios adicionales e incluso algunas secuencias de configuración (como el registro) por brevedad. Como puedes ver, tengo algunas funciones genéricas además de soporte tanto para PyVISA como para USBTMC. La mayoría de las bibliotecas de Python basadas en SCPI que encuentres en línea no tendrán esta funcionalidad base. Eso está bien porque puedo extender esta clase en mi nueva clase:
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()
# … Código ha sido eliminado por brevedad
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]),
"potencia": float(meas[2]),
}
class DP712(DP):
def channel_check(self, canal):
assert canal in [1, ""], f"Canal de salida {canal} no soportado"
class DP821(DP):
def channel_check(self, canal):
assert canal in [1, 2, ""], f"Canal de salida {canal} no soportado"
class DP832(DP):
def channel_check(self, canal):
assert canal in [1, 2, 3, ""], f"Canal de salida {canal} no soportado"
He tomado varios ejemplos diferentes en línea y los he pasado a ChatGPT. Le he solicitado que:
Utilizar la IA generativa para esta tarea redujo un trabajo de varias horas a 60 segundos. También pude validar muy rápidamente los controladores que encontré en línea ejecutando las pruebas autogeneradas que ChatGPT escribió para mí. Por ejemplo, descubrí, muy rápidamente, que uno de los comandos SCPI para el DL3021 para establecer el modo de operación de la carga electrónica era en realidad incorrecto. Al observar las pruebas ejecutarse en tiempo real, noté que el modo no estaba cambiando en mi instrumento. Una rápida consulta en el manual SCPI y pude corregirlo.
En este punto tenemos una buena base para nuestra biblioteca de Python para controlar nuestros instrumentos. El objetivo ahora es colocar un servicio web frente a la biblioteca de instrumentos, dándonos la capacidad de controlarlos a través de la web. Suponiendo que no sé nada sobre marcos web, simplemente puedo pedirle a ChatGPT (o cualquier LLM) que realice toda la función por mí. Aquí está la indicación que utilicé para comenzar:
Necesito crear un servicio web que controle mi instrumento usando URLs básicas (a través de un navegador web). No sé mucho sobre servicios web o marcos, pero estoy familiarizado con Python. Escribe el código completo de Python para lograr esta tarea.
Aquí está la clase que controla mi instrumento:
<pre><code>```
{código de arriba}
```
</code></pre>
Y la (respuesta parcial):
Figura 2: Respuesta de ChatGPT a un prompt
Mi parte favorita sobre los LLM sofisticados (de pago) es que realmente desglosan la tarea para ti, te educan y proporcionan una solución bastante buena como primera iteración. En Gemini vs. ChatGPT: ¿Quién escribe mejor código? comparé los dos LLM entre sí y (alerta de spoiler) descubrí que la última versión más avanzada de Gemini de Google era realmente bastante buena (si no a la par con ChatGPT 4). Usar cualquiera de estos LLM dará un resultado similar al mostrado arriba.
Si quiero poner esto en una clase o reformatearlo, simplemente puedo responder al chat y hacer mi solicitud. ¿Quieres que tu código esté comentado? No hay problema, ¡solo pregunta!
Prompt: Añade bloques de comentarios de estilo docstring a cada función. Reescribe toda la clase como resultado para que pueda copiarla y pegarla de vuelta en mi editor de código.
Figura 3: Siguiente respuesta del prompt de ChatGPT
Ahora que hemos hecho que la IA Generativa cree el módulo de Python, podemos pedirle que escriba pruebas para nosotros o que nos muestre cómo probar manualmente:
Figura 4: URL de prueba manual de ChatGPT
Figura 5: Prueba automatizada escrita por ChatGPT
Después de ejecutar (o ajustar y luego ejecutar) estas pruebas, deberíamos poder validar nuestro nuevo módulo de Python que hace de interfaz entre la web y nuestro instrumento.
En este artículo reunimos un módulo de Python que controlaba nuestro instrumento aprovechando el código existente en la web y la IA Generativa. Después de que nuestro LLM completara el módulo, introdujimos un nuevo módulo que actuaba como intermediario entre Internet y el propio instrumento (a través de un servicio web). También echamos un vistazo rápido a cómo la IA Generativa puede guiarnos a través de todo el proceso incluyendo pruebas manuales o automatizadas. La mejor parte de este ejercicio es que, si se hace correctamente, debería haberte llevado muy poco tiempo armarlo. La primera vez podría requerir un esfuerzo extra pero repetir este proceso para muchos instrumentos debería ser pan comido (e incurrir en muy poco tiempo de desarrollo). Está claro que la IA Generativa ha cambiado completamente el panorama para todos nosotros y este artículo realmente lo demuestra. Esperamos que te sientas inspirado para crear el próximo conjunto de bibliotecas de instrumentos aprovechando la IA Generativa y contribuirlos de vuelta al resto de la comunidad.
Todo el código fuente utilizado en este proyecto se puede encontrar en: https://gitlab.com/ai-examples/instrument-controllables.