Skip to content

Latest commit

 

History

History
276 lines (209 loc) · 12.1 KB

File metadata and controls

276 lines (209 loc) · 12.1 KB

Русский · English · Deutsch · Español · 中文

Cámara térmica UNI-T UTi260M en macOS

Una aplicación sencilla que permite usar la cámara térmica UNI-T UTi260M en un Mac. El módulo se vende como accesorio para teléfonos Android (conector Type-C), pero es una cámara USB corriente, y a macOS también se le puede sacar temperaturas reales.

Característica Valor
Resolución del sensor 256 x 192
Rango de temperatura -20 ... 550 °C
Campo de visión (FOV) 56° x 42°
Conector USB Type-C (pensado para Android)
Cómo lo ve el sistema USB Camera, VID 0x0BDA, PID 0x5830

UNI-T UTi260M

macOS trata ese módulo como una webcam normal, así que Photo Booth muestra una silueta gris sin temperatura alguna. Esta aplicación lee los datos térmicos en bruto y construye con ellos un mapa de temperaturas calibrado, con escala en °C, marcadores del punto más caliente y más frío y medición por clic.

captura

Descargar la app ya construida

La Thermal.app compilada va adjunta en las releases - no hay que compilar nada, Python y todas las bibliotecas ya están dentro.

  • macOS 13 o posterior, solo Apple Silicon (M1 en adelante)
  • descarga el archivo, descomprímelo y arrastra Thermal.app a Aplicaciones
  • en el primer arranque haz clic derecho en el icono y elige «Abrir», si no macOS se niega a ejecutarla: la firma es ad-hoc, sin certificado de pago de Apple. Se pregunta una sola vez.

Lo mismo desde la terminal:

xattr -dr com.apple.quarantine /Applications/Thermal.app

Para un Mac Intel, o para tu propia compilación, tienes build_app.sh más abajo.

Probado solo con la UTi260M

Todo se escribió y depuró con una única UNI-T UTi260M. Otros módulos con el mismo núcleo (serie UNI-T UTi, InfiRay P2 Pro, Topdon TC001) están construidos de forma parecida y lo más probable es que funcionen, pero no se han probado - si tienes uno, abre un issue y lo añado a la lista.

Por qué fallan aquí los programas habituales

La cámara expone dos modos UVC:

Modo Contenido
256x192 yuvs solo la imagen del sensor tras el AGC, una silueta gris, sin temperaturas
256x384 yuvs imagen más el mapa de temperaturas en bruto

En el modo 256x384 el fotograma son dos bloques apilados:

  • filas 0..191 - imagen del sensor de 8 bits (YUYV, luminancia en los bytes pares);
  • filas 192..383 - 16 bits en bruto little-endian por píxel.

Temperatura: T(°C) = raw / 64 - 273.15

Photo Booth y FaceTime eligen 256x192, de ahí la silueta sin cifras.

Los scripts ya hechos como PyThermalCamera o P2Pro-Viewer se apoyan en OpenCV. En macOS cv2.VideoCapture pasa por AVFoundation, que convierte YUYV a RGB en silencio: los 16 bits en bruto se pierden sin remedio y solo quedan colores bonitos sin relación con la temperatura. Aquí se evita abriendo AVCaptureVideoDataOutput con el formato de píxel 'yuvs' (0x79757673) solicitado de forma explícita y leyendo los bytes de CVPixelBuffer tal cual, respetando bytesPerRow.

Requisitos

  • macOS 13 o posterior
  • Python 3 en compilación framework de python.org (el backend macosx de matplotlib la necesita)
  • La propia UTi260M

Ejecución

git clone https://github.com/mamapapa1/uti260m-macos.git
cd uti260m-macos
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
./venv/bin/python -m unithermal

Construir una app que se abre con doble clic:

./build_app.sh              # → ~/Desktop/Thermal.app
./build_app.sh /Applications

En el primer arranque macOS pide acceso a la cámara, hay que concederlo. La aplicación lo solicita de forma explícita: sin eso el bundle no recibe ni fotogramas ni diálogo.

También puedes ver la aplicación sin módulo alguno:

./venv/bin/python -m unithermal --demo

El modo demo dibuja fotogramas sintéticos, pero los empaqueta en el mismo formato de transmisión y los pasa por la misma decodificación que los reales, así que se ejecuta el programa entero y no una rama recortada.

La cámara no hace falta al arrancar: la ventana se abre enseguida y avisa de que espera el módulo. Al conectarlo la imagen aparece sola; al desconectarlo la ventana no se cierra, vuelve a esperar. La captura se reinicia sola, así que puedes enchufar y desenchufar el USB las veces que quieras.

Las capturas con s van a ~/Pictures/Thermal/ si la app se ejecuta desde el bundle (allí el directorio de trabajo es /), y si no, al directorio actual. Cada captura es un PNG más un CSV con la matriz completa de 192x256 en grados Celsius.

Teclas

r / R girar 90° en sentido horario / antihorario
m espejo horizontal
espacio congelar / soltar la imagen
f pantalla completa (Esc para salir)
p siguiente paleta
a rango de temperatura automático / fijo
c temperaturas / imagen propia del sensor
clic colocar un punto de medición
s guardar PNG + CSV
l siguiente idioma de la interfaz
h mostrar / ocultar la ayuda
q salir

Abajo del todo siempre hay una línea de ayuda: al arrancar desde el bundle no hay terminal, así que la lista de teclas tiene que vivir en la propia ventana.

Distribuciones de teclado

Funciona cualquier distribución. matplotlib entrega un carácter y no una tecla, así que con la distribución rusa la R física llega como к. La aplicación le pregunta al propio sistema cuál es la distribución activa (Text Input Services) y con UCKeyTranslate averigua qué carácter da cada tecla física, para luego traducir la pulsación por posición de la tecla. No hay tablas por idioma: el ucraniano, el griego, el armenio, el hebreo y cualquier otra distribución funcionan igual, y un cambio de distribución en caliente se detecta en medio segundo.

Se conserva el uso de mayúsculas, porque distingue el sentido del giro, y también los modificadores (ctrl+кctrl+r). Si la API del sistema no está disponible, queda una tabla ЙЦУКЕН incorporada. Las distribuciones latinas como AZERTY o QWERTZ no necesitan traducción: las letras son las mismas, solo están en otro sitio.

Idioma de la interfaz

La interfaz sigue el idioma de macOS y está traducida al ruso, inglés, alemán, español, francés y chino. Se puede forzar con --lang:

./venv/bin/python -m unithermal --lang zh

Dentro de la ventana el idioma se elige pulsando el botón con el código de idioma de la esquina superior izquierda: despliega una lista donde cada idioma está escrito en sí mismo. La tecla l los recorre en círculo. La elección se recuerda: queda en ~/Library/Application Support/Thermal/settings.json y se aplica en el siguiente arranque.

Para el chino la aplicación cambia a una fuente CJK (PingFang SC y alternativas): la fuente por defecto de matplotlib, DejaVu Sans, no tiene ideogramas y dibujaría cuadros vacíos sin dar ningún error.

Línea de comandos

--snapshot PATH    guardar un solo fotograma en PNG y salir
--offset DELTA_C   corrección en °C sobre todos los valores (calibración con una referencia)
--device UNIQUE_ID elegir cámara si hay más de una
--list             listar las cámaras compatibles y salir
--demo             funcionar con fotogramas sintéticos, sin cámara
--lang CODE        en, ru, de, es, fr, zh

Precisión

  • La resolución en temperatura es de 1/64 K, pero la precisión absoluta de estos módulos ronda los ±2 °C, y la calibración de fábrica supone una emisividad de ε ≈ 0.95.
  • El metal brillante, el vidrio y el plástico pulido dan lecturas muy bajas: ahí se mide radiación reflejada y no la superficie en sí. La solución habitual es un trozo de cinta mate sobre el punto a medir.
  • El sensor hace FFC de vez en cuando (un clic del obturador, la imagen se congela un instante). Es normal, sin eso la imagen deriva.
  • --offset aplica una corrección lineal al comparar con un termómetro de referencia.

La cámara se busca por la presencia del modo 256x384 y no por el uniqueID, porque este último cambia según el puerto USB.

Trampas al empaquetar en .app

Cuatro trampas costaron aquí un tiempo notable; todas están resueltas en build_app.sh y en el código. Las dos primeras dan el mismo síntoma: el doble clic no hace nada, no hay error, no se genera crash report, y sin embargo ese mismo lanzador desde el terminal funciona.

  1. Un symlink que sale del bundle. python -m venv deja bin/python3.x como enlace a /Library/Frameworks/.... La firma de código prohíbe los symlinks hacia fuera (invalid destination for symbolic link in bundle), la firma queda rota y Gatekeeper bloquea el arranque. Se arregla copiando el binario real dentro.
  2. Rosetta. El ejecutable principal del bundle es un script de shell, que no tiene arquitectura, así que LaunchServices lanza el proceso como x86_64 bajo Rosetta mientras que las ruedas de numpy/pyobjc son solo arm64. Cerrado por tres vías a la vez: lipo -thin para el intérprete, LSArchitecturePriority en Info.plist y exec /usr/bin/arch en el lanzador.
  3. Acceso a la cámara. Una app empaquetada no recibe el diálogo automáticamente, la sesión simplemente no entrega fotogramas. Hay que llamar explícitamente a AVCaptureDevice.requestAccessForMediaType_completionHandler_.
  4. Composición. colorbar(ax=...) y set_title quitan espacio a los ejes y aprietan el fotograma hasta unos dos tercios de la ventana; en pantalla completa la imagen ocupaba solo el 77 % de la altura. Ahora los ejes se extienden por todo el lienzo y todas las etiquetas son superposiciones, con lo que se llega al 100 %.

Para depurar un bundle que muere en silencio, redirige la salida del lanzador a un fichero (exec >> ~/thermal-launch.log 2>&1 como primera línea), si no el traceback se pierde.

spctl -a devuelve rejected con una firma ad-hoc. Es lo esperado sin notarización y no impide arrancar: un bundle compilado localmente no lleva atributo de cuarentena.

Si algo va mal

  • «Cámara térmica no encontrada» → el módulo está ocupado por otra app (Photo Booth, FaceTime, Zoom, una copia antigua de esta misma app). Ciérralas y comprueba con --list.
  • La ventana se abre pero no llegan fotogramas → no se concedió acceso a la cámara: Ajustes del Sistema → Privacidad y seguridad → Cámara, actívala para «Thermal».
  • Cuadros vacíos en lugar de ideogramas → el sistema no tiene ninguna de las fuentes CJK de la lista, instala PingFang SC o arranca con --lang en.
  • El doble clic en la .app no hace nada → mira las trampas de empaquetado de arriba.

Tests

./venv/bin/pip install pytest
./venv/bin/python -m pytest tests -q

La batería cubre la traducción de distribuciones (incluidos todos los atajos escritos en ruso), la integridad de las traducciones y sus sustituciones para los seis idiomas, la cobertura de glifos de la fuente elegida, la geometría del giro (la relación de aspecto del píxel debe quedarse exactamente en 1.0 en las cuatro orientaciones), la parte de la pantalla que ocupa el fotograma en pantalla completa, la congelación, el funcionamiento sin módulo conectado con reconexión en caliente y el cambio de idioma en la ventana.

Aviso legal

Un proyecto aficionado, no afiliado a UNI-T. La aplicación solo lee lo que entrega el módulo y no es un instrumento de medida: las lecturas dependen de la emisividad de la superficie, de la distancia y de lo caliente que esté la propia cámara. Donde las medidas importen, hace falta un instrumento calibrado.

Licencia

GNU GPL v3 © 2026 Alexey Verhogladov.

💚 Apoyar

El proyecto es gratis y hecho por entusiasmo. Si te sirvió - gracias por el apoyo (USDT):

Red Dirección
USDT · TRC20 TYLmBvdxL8t9ziyZiFp3jcHwQbAsZR5haZ
USDT · TON UQCnVND-uBgkWIAD1UP14tsN8239KE5BTOfKSJmg-0XqrN-k
USDT · ERC20 0xa61dcA98A86D84883Ddb3d62aA7F008f157c8eFF