Es un artículo un poco largo, pero así me desahogo. Si quieres ir a lo grano, a lo técnico:
Descarga la conversación completa con ChatGPT aquí y el código del programa Python la tienes en este repositorio de GitHub
Tres cosas que he aprendido
- A usar la IA (ChatGPT) para hacerme una idea de cómo acceder a una DLL con Python
- A revisar el código y probar cosas absurdas pero lógicas
- A aislar el problema para focalizarme en la solución
La historia
Estas últimas semanas andaba liado con un problema en mi trabajo actual. En la aplicación para Desktop hemos dicho adiós al 32bits y hemos pasado a los 64bits. Por un lado bien, pero por otro mal. Mal porque la aplicación Desktop para un entorno empresarial se conecta a cacharritos (devices) externos como impresoras de etiquetas, básculas, dispositivos de firma, etc. que aun siguen estando en x32. Y como siempre suele pasar, no se puede prever todo lo que puede ir mal. Y un cliente en concreto tiene un desarrollo a medida que le hicimos para poder conectar con un lector de firmas llamado epadInk.
La teoría decía que seguramente el problema de falta de comunicación después de haber actualizado a x64 se solventaría actualizando sus drivers/software a la versión de x64. La conexión se realiza usando la DLL u OCX proporcionado por el fabricante, y el lenguaje de programación Cosmos se conecta vía CLSID que está grabado en el registro de Windows. Pensamos que seguramente, al actualizar los drivers/software el identificador CLSID había cambiado, y así era en las máquinas locales. Solo faltaba modificar el identificador para ponerle el nuevo y sería un éxito. Y lo fue al 50%, porque resultó que el epadInk se conecta localmente al puerto USB de la máquina de la tienda, pero para acceder a nuestra aplicación Desktop las tiendas se conectan vía Terminal Server.
En este punto, admito que me explotó la cabeza. No llegaba a entender como un aparato que estaba en local, podía haber funcionado y enviado la información necesaria a la aplicación Desktop que se ejecutaba en el Terminal Server. Cuando me recompuse, comenzó una nueva investigación. Manuales para arriba, manuales para abajo, instala esto, instala aquello… Sin éxito. El horizonte comenzó a despejarse cuando conseguimos un cacharrito similar al que tenía el cliente. Ahora al menos, podíamos simular un entorno igual al que tenía el cliente.
Con entorno pero sin éxito
Tras varias consultas al servicio de atención al cliente del aparatito, conseguimos instalar tanto en el Terminal Server como en local el software de las versiones adecuadas para hacer la conexión. Es más, no cabía de júbilo cuando se encendió la pantallita del epadInk conectado a USB en mi máquina local, pero lanzada la petición desde el programa de test que trae el software del fabricante. Pensé, ya está. Ahora (aunque no entiendo cómo se lo montan internamente) bastará con revisar que el CLSID es correcto y si no lo es, cambiarlo y a correr.
Pero no iba a ser tan sencillo, por supuesto. El CLSID era el mismo, pero seguía sin conseguir que se conectara. Nuevas consultas al soporte del fabricante me dieron pistas para usar otro componente OCX que traía el software. Destripé el OCX para saber las funciones que contenía, revisé el código en C# que traía de ejemplo (honestamente intuí lo que hacía, pues no tengo conocimientos de ese lenguaje) y revisé una y otra vez mi código en Cosmos. Seguía sin ir estaba haciendo lo mismo que el ejemplo del fabricante. Le pedí si me podían crear un código de ejemplo para VB6 o VBA, que se parecería aún más a lo que es el lenguaje de programación que utilizo. Pero la respuesta fue que no, ellos no podían hacer ese ejemplo, pero que si les mandaba la secuencia de métodos a los que yo atacaba, podrían intentar ayudarme a identificar dónde estaba el problema.
Reduje todo mi código a una función con comentarios para que vieran los pasos uno a uno que hacía a ver si ellos me sabían qué podía fallar y se lo envié. No me quedé tranquilo. Tenía una espinita clavada. Y pensé: «Y si pruebo a conectarme con Python». Algo de Python sé, que si las variables, que si el print, oigo hablar mucho de diccionarios, etc. Pero creer que podría desde cero hacer un programa en Python que se conectara a una DLL y llamara a sus propiedades y funciones, es cuando menos, una locura. Pero le pedía ayuda a ChatGPT.
ChatGPT me hace un programa Python
Mi primera pregunta a la IA fue un tanto escéptica. Pensé: «No se pierde nada, pero este empezará con su verborrea del año 2021 y verás que no sirve, pero por probar». Así que mi consulta fue: crea un ejemplo de usar con python un activex al que se conecta por clsid
Y si algo tiene la IA es que es obediente y educada. Y me escupió este código:
import win32com.client
def main():
try:
# CLSID del objeto ActiveX (por ejemplo, Microsoft Excel)
clsid = "{00024500-0000-0000-C000-000000000046}" # CLSID de Excel.Application
# Crear una instancia del objeto ActiveX
excel = win32com.client.Dispatch(clsid)
# Hacer algo con el objeto ActiveX (en este caso, abrir un libro de Excel)
excel.Visible = True
workbook = excel.Workbooks.Add()
worksheet = workbook.Worksheets(1)
worksheet.Cells(1, 1).Value = "Hola desde Python"
# Guardar y cerrar el libro de Excel
workbook.SaveAs("ejemplo.xlsx")
workbook.Close()
# Cerrar la instancia de Excel
excel.Quit()
print("¡Proceso completado con éxito!")
except Exception as e:
print("Ocurrió un error:", e)
if __name__ == "__main__":
main()
Así que interpreté que esto habría el Excel vía ActiveX. Vale me sirve para ver si funciona. Copio el programita en Python y me canta que no sabe que es win32com.client. ¡Madre! ¿Y cómo se hacía eso de instalar dependencias o librerías, o como se llame? Bueno, segunda pregunta para ChatGPT: como se instala win32com.cliente. Su respuesta fue:
pip install pywin32
Pues nada, le damos a copiar y pegar. Y me salta un error en la línea 5. Y un poco cabreado le contesto a ChatGPT: da error la linea 5. ¡Hombre! que no estoy para perder el tiempo, a mi dame las cosas masticaditas. A lo que tras un disculpa por su parte, me indica que seguramente no he ejecutado correctamente el comando anterior, y tiene razón, introduje un error tipográfico.
Ejecuto el programa, y oye, una vez apuntada la hoja de Excel que existía, pues se abre. Pienso: «Bueno, mira, parece que avanzamos. Pero voy a probar a ver si tiene en sus conocimientos conectar con el aparatito este epadInk» Y le lance esta pregunta: crea un ejemplo de usar con python un activex al que se conecta por clsid de epading. Reconozco que la desesperación me llevó a ir a por todas, pero oye, es gratis. Su respuesta no fue algo ambigua:
import win32com.client
def main():
try:
# CLSID específica de ePadLink (debes reemplazarla con la CLSID correcta)
clsid = "{TU_CLSID_AQUI}"
# Crear una instancia del objeto ActiveX
epad = win32com.client.Dispatch(clsid)
# Realizar operaciones con el dispositivo ePadLink
print("Conexión exitosa con ePadLink.")
except Exception as e:
print("Ocurrió un error:", e)
if __name__ == "__main__":
main()
Bueno, vale, puede servirme. El CLSID lo tengo, por probar a ver si da error no pierdo nada. ¡Bingo! No da error, y dice que se ha conectado. ¡Ey! Pero podría decir también que sabe mandar un cohete a la luna, porque el control de si se conecta o no es un poco… chusco por decirlo finamente. Pero vamos a seguir jugando. Sé que según el manual, hay pasarle al aparato unas propiedades que ya conozco por que son las que tengo en mi código de Cosmos, pero no sé cómo se graban y leen esas propiedades en Python. Pero la IA lo sabe todo, pregunto a ver: «siguiendo con este ejemplo, cómo haría para leer y escribir las propiedades del activex». Su respuesta:
import win32com.client
def main():
try:
# Reemplaza "{TU_CLSID_AQUI}" con la CLSID específica de ePadLink
clsid = "{TU_CLSID_AQUI}"
# Crear una instancia del objeto ActiveX
epad = win32com.client.Dispatch(clsid)
# Leer una propiedad existente
print("Versión del software de ePadLink:", epad.Version)
# Escribir en una propiedad existente (si es posible)
# Por ejemplo, para configurar el tamaño de la fuente en 12 puntos
epad.FontSize = 12
print("Configuración de tamaño de fuente aplicada correctamente.")
except Exception as e:
print("Ocurrió un error:", e)
if __name__ == "__main__":
main()
Se ha tirado un triple y me da alguna pista. Ahora vamos a probar con las funciones y métodos. Sigo mi charla: «siguiendo con este ejemplo, cómo haría llamar a las funciones o metodos del activex». Reconozco que no se lo pongo fácil, porque me puedo expresar mejor, pero no se desinfla y contesta:
import win32com.client
def main():
try:
# Reemplaza "{TU_CLSID_AQUI}" con la CLSID específica de ePadLink
clsid = "{TU_CLSID_AQUI}"
# Crear una instancia del objeto ActiveX
epad = win32com.client.Dispatch(clsid)
# Llamar a un método existente con argumentos
epad.StartCapture()
print("Captura iniciada correctamente.")
# Llamar a otro método que devuelve un valor
status = epad.GetStatus()
print("Estado actual del dispositivo ePadLink:", status)
except Exception as e:
print("Ocurrió un error:", e)
if __name__ == "__main__":
main()
Con todo esto, apoyado por el manual y lo que pone en mi código, creo las llamadas a las propiedades y funciones/métodos del ActiveX. Y consigo recoger el número de dispositivo y limpiar la pantalla de firma. Parece que estoy bien encaminado, aunque no he llegado al punto clave que es guardar la firma en una imagen.
Me pongo creativo, y le pido a ChatGPT: «como hacer una pausa en un programa de python». A lo que me responde:
import time
print("El programa está en pausa durante 5 segundos...")
time.sleep(5)
print("La pausa ha terminado.")
Y lo pego y adapto en el sitio en concreto de mi programita Python. Pero al ejecutar me canta un error que se lo paso a ChatGPT: «time no está definido». Amablemente me responde
Si
time
no está definido, es posible que no hayas importado correctamente el módulotime
. Asegúrate de agregar la líneaimport time
al principio de tu script. Aquí te muestro cómo debería lucir:
import time
print("El programa está en pausa durante 5 segundos...")
time.sleep(5)
print("La pausa ha terminado.")
Y es verdad, añado el trocito a mi código. No recuerdo muy bien para qué, pero se me ocurre hacer una parada en el programa para que el usuario diga si quiere o no grabar la firma en imagen. Eso lo vi en algún curso de Python, pero no recuerdo la sintaxis. Amigo ChatGPT: «crea un input para pedir s/N»
def main():
respuesta = input("¿Quieres continuar? (s/N): ").strip().upper()
if respuesta == 'S':
print("Continuando...")
elif respuesta == 'N':
print("Saliendo...")
else:
print("Respuesta no válida. Por favor, ingresa 's' para sí o 'N' para no.")
if __name__ == "__main__":
main()
Pillo lo que me interesa y lo meto en el programa. Después de probar, pienso que no me hace falta preguntar nada y que hay parte de código que no quiero perder pero sí comentar. En no sé qué video de Youtube enseñaban a comentar varias líneas. ¿Era tres comillas, cuatro?. Pregunta para ChatGPT: como comentar varias lineas en python.
'''
Este es un comentario
que abarca varias líneas.
Puedes escribir cualquier cosa aquí
sin afectar el funcionamiento del programa.
'''
print("Hola mundo")
Sigo con preguntas sobre como montar el if, cómo salir del programa, pues estoy añadiendo controles… y llego al momento de grabar la imagen. Y efectivamente se realiza desde Python todo correctamente. Eso despeja de mi mente la duda de si realmente la secuencia de propiedades y métodos que llamaba en mi código Cosmos era correcta. ES CORRECTO. Reviso el código, y no veo nada. Reviso las funciones que llamo y no veo nada. Reviso que tenga permisos en la carpeta donde quiero grabar la imagen y es correcto.
La solución estaba en mi cabeza
Y es aquí donde la suerte, la fortuna, la inspiración, la potra o como lo quiera llamar cada uno, aparece como salvador. Pienso: «El manual dice que cuando llamas a la función para grabar la imagen, no devuelve ningún valor, y es cierto, porque en Python no devuelve nada. ¿Y si en cosmos sí que tiene reservada la primera posición de los parámetros para recibir un valor, aunque no reciba nada, y comienzan los parámetros que envío una posición más allá?» EFECTIVAMENTE. El gran lenguaje Cosmos tiene la primera posición reservada para recibir el resultado, sea que la función devuelva o no algo.
Este es un momento único. El sentimiento es variado. Por un lado, maldigo, al lenguaje, a la documentación. Por otro, de euforia. He resuelto un problema. Y no habría llegado a esta conclusión si ChatGPT me hubiera escrito un ejemplo de como acceder a un ActiveX en lenguaje Python, que me ayudó a descartar el mal funcionamiento del aparato.
Si quieres leer la conversión completa con ChatGPT la puedes ver aquí y el código del programa Python la tienes en este repositorio de GitHub