Русский · English · Deutsch · Español · 中文
Простое приложение, которое даёт работать с тепловизором UNI-T UTi260M на маке. Модуль продаётся как приставка к Android-телефону (разъём Type-C), но это обычная USB-камера, и на macOS её тоже можно заставить показывать температуры.
| Характеристика | Значение |
|---|---|
| Разрешение сенсора | 256 x 192 |
| Диапазон температур | -20 ... 550 °C |
| Поле зрения (FOV) | 56° x 42° |
| Разъём | USB Type-C (штатно для Android) |
| Как виден системе | USB Camera, VID 0x0BDA, PID 0x5830 |
macOS считает такой модуль обычной веб-камерой, поэтому Photo Booth показывает серый силуэт вообще без температур. Это приложение читает сырые тепловые данные и строит из них калиброванную температурную карту со шкалой °C, маркерами горячей и холодной точек и замером по клику.
Собранное Thermal.app лежит в релизах - собирать ничего не надо, python и все библиотеки уже внутри.
- macOS 13 или новее, только Apple Silicon (M1 и новее)
- скачать архив, распаковать, перетащить
Thermal.appв «Программы» - первый запуск - правой кнопкой по значку и «Открыть», иначе macOS откажется его запускать: подпись ad-hoc, без платного сертификата Apple. Спрашивается один раз.
То же самое из терминала:
xattr -dr com.apple.quarantine /Applications/Thermal.appДля Intel-мака или своей сборки есть build_app.sh, см. ниже.
Всё написано и отлажено на одном экземпляре UNI-T UTi260M. Другие модули на том же ядре (серия UNI-T UTi, InfiRay P2 Pro, Topdon TC001) устроены похоже и, скорее всего, заработают, но не проверялись - если у вас такой, отпишитесь в Issues, добавлю в список.
Камера отдаёт два UVC-режима:
| Режим | Что внутри |
|---|---|
256x192 yuvs |
только картинка сенсора после AGC, серый силуэт, без температур |
256x384 yuvs |
картинка плюс сырая температурная карта |
В режиме 256x384 кадр склеен из двух блоков:
- строки
0..191- 8-битная картинка сенсора (YUYV, яркость в чётных байтах); - строки
192..383- сырые 16 бит little-endian на пиксель.
Температура: T(°C) = raw / 64 - 273.15
Photo Booth и FaceTime выбирают режим 256x192, отсюда и силуэт без цифр.
Готовые скрипты вроде PyThermalCamera или P2Pro-Viewer опираются на OpenCV. На
macOS cv2.VideoCapture идёт через AVFoundation, который молча конвертирует YUYV в
RGB: сырые 16 бит теряются безвозвратно, остаются только красивые цвета без привязки
к температуре. Здесь это обходится открытием AVCaptureVideoDataOutput с явно
запрошенным пиксельным форматом 'yuvs' (0x79757673) и чтением байтов
CVPixelBuffer как есть, с учётом bytesPerRow.
- macOS 13 или новее
- Python 3 сборки python.org (macosx-бэкенду matplotlib нужна именно framework-сборка)
- Сам тепловизор UTi260M
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Собрать приложение, запускаемое двойным кликом:
./build_app.sh # → ~/Desktop/Thermal.app
./build_app.sh /ApplicationsПри первом запуске macOS спросит доступ к камере, надо разрешить. Приложение запрашивает его явно: без этого собранный бандл не получает ни кадров, ни диалога.
Посмотреть на приложение можно и вовсе без модуля:
./venv/bin/python -m unithermal --demoДемо-режим рисует синтетические кадры, но собирает их в том же проводном формате и пропускает через тот же разбор, что и настоящие, так что работает вся программа целиком, а не урезанная ветка.
Сам тепловизор при запуске не обязателен: окно открывается сразу и пишет, что ждёт модуль. Воткнули - картинка появляется сама; выдернули - окно не закрывается, а снова ждёт. Захват переподнимается сам, так что USB можно передёргивать сколько угодно.
Снимки по s кладутся в ~/Pictures/Thermal/, если запуск был из бандла (рабочий
каталог там /), иначе в текущий каталог. Каждый снимок - это PNG плюс CSV с полной
матрицей 192x256 в градусах Цельсия.
r / R |
поворот на 90° по / против часовой стрелки |
m |
зеркало по горизонтали |
пробел |
стоп-кадр: заморозить / отпустить картинку |
f |
во весь экран (Esc - выйти) |
p |
следующая палитра |
a |
авто / фиксированный диапазон температур |
c |
температуры / собственная картинка сенсора |
| клик | поставить точку замера |
s |
сохранить PNG + CSV |
l |
следующий язык интерфейса |
h |
показать / скрыть панель справки |
q |
выход |
Внизу окна всегда висит строка-подсказка: при запуске из бандла терминала нет, поэтому список клавиш должен жить прямо в окне.
Работает любая раскладка. matplotlib отдаёт в событии символ, а не клавишу, поэтому при
русской раскладке физическая R приходит как к. Приложение спрашивает текущую
раскладку у самой системы (Text Input Services) и через UCKeyTranslate выясняет, какой
символ даёт каждая физическая клавиша, после чего переводит нажатие обратно по позиции
клавиши. Таблиц под каждый язык нет: так же работают украинская, греческая, армянская,
иврит и любая другая раскладка, а переключение прямо во время работы подхватывается за
полсекунды.
Регистр сохраняется, он различает направление поворота, и модификаторы тоже
(ctrl+к → ctrl+r). Если системный API недоступен, остаётся встроенная таблица ЙЦУКЕН.
Латинским раскладкам вроде AZERTY или QWERTZ перевод не нужен: буквы там те же самые,
просто на других местах.
Интерфейс следует языку macOS и переведён на русский, английский, немецкий,
испанский, французский и китайский. Переопределяется ключом --lang:
./venv/bin/python -m unithermal --lang zhПрямо в окне язык выбирается кликом по кнопке с кодом языка в левом верхнем углу: она
раскрывает список, где языки написаны на себе самих. Клавиша l листает их по кругу.
Выбор запоминается: он лежит в
~/Library/Application Support/Thermal/settings.json и подхватывается при следующем
запуске.
Для китайского приложение переключается на CJK-шрифт (PingFang SC и запасные): шрифт matplotlib по умолчанию, DejaVu Sans, не содержит иероглифов и рисовал бы пустые квадраты, не выдавая при этом никакой ошибки.
--snapshot PATH сохранить один кадр в PNG и выйти
--offset DELTA_C поправка в °C ко всем значениям (калибровка по эталону)
--device UNIQUE_ID выбрать камеру, если их несколько
--list показать подходящие камеры и выйти
--demo работать на синтетических кадрах, без камеры
--lang CODE en, ru, de, es, fr, zh
- Разрешение по температуре 1/64 K, но абсолютная точность таких модулей порядка ±2 °C, а заводская калибровка рассчитана на коэффициент излучения ε ≈ 0.95.
- Блестящий металл, стекло и полированный пластик сильно занижают показания, по ним меряется отражённое излучение, а не сама поверхность. Обычное решение - матовая изолента на участок замера.
- Сенсор периодически делает FFC (щелчок затвора, кадр на мгновение замирает). Это норма, без неё картинка «плывёт».
--offsetдаёт линейную поправку, если сверять с эталонным термометром.
Камера ищется по наличию режима 256x384, а не по uniqueID, потому что последний
меняется в зависимости от USB-порта.
Четыре ловушки стоили здесь заметного времени; все они закрыты в build_app.sh и в
коде. Первые две дают одинаковый симптом: двойной клик не делает ничего, ошибки нет,
crash report не создаётся, а запуск того же лаунчера из терминала при этом работает.
- Симлинк за пределы бандла.
python -m venvделаетbin/python3.xсимлинком на/Library/Frameworks/.... Подпись кода запрещает симлинки наружу (invalid destination for symbolic link in bundle), подпись получается битой, и Gatekeeper блокирует запуск. Лечится копированием реального бинаря внутрь. - Rosetta. Главный исполняемый файл бандла - shell-скрипт, у которого нет
архитектуры, поэтому LaunchServices запускает процесс как x86_64 под Rosetta, а
колёса numpy/pyobjc собраны только под arm64. Закрыто тремя способами сразу:
lipo -thinдля интерпретатора,LSArchitecturePriorityвInfo.plistиexec /usr/bin/archв лаунчере. - Доступ к камере. Собранное приложение не получает диалог автоматически, сессия
просто не отдаёт кадры. Нужно явно вызывать
AVCaptureDevice.requestAccessForMediaType_completionHandler_. - Компоновка.
colorbar(ax=...)иset_titleзабирают место у осей, ужимая кадр примерно до двух третей окна; в полноэкранном режиме картинка занимала лишь 77% высоты экрана. Теперь оси растянуты на весь холст, а все подписи сделаны оверлеями, и получается 100%.
Чтобы отладить бандл, который молча умирает, перенаправьте вывод лаунчера в файл
(exec >> ~/thermal-launch.log 2>&1 первой строкой), иначе traceback уходит в никуда.
spctl -a показывает rejected для ad-hoc подписи. Это ожидаемо без нотаризации и
запуску не мешает: у локально собранного бандла нет атрибута карантина.
- «Тепловизор не найден» → модуль занят другим приложением (Photo Booth, FaceTime,
Zoom, старая копия этого же приложения). Закрыть их и проверить
--list. - Окно открылось, но кадров нет → не выдан доступ к камере: Системные настройки → Конфиденциальность и безопасность → Камера, включить для «Thermal».
- Вместо иероглифов пустые квадраты → в системе нет ни одного CJK-шрифта из списка,
поставить PingFang SC или запустить с
--lang en. - Двойной клик по .app не делает ничего → смотреть раздел про грабли упаковки выше.
./venv/bin/pip install pytest
./venv/bin/python -m pytest tests -qНабор покрывает перевод раскладок (включая все горячие клавиши по-русски), полноту переводов и подстановки для всех шести языков, покрытие глифов выбранным шрифтом, геометрию поворота (соотношение сторон пикселя должно оставаться ровно 1.0 во всех четырёх ориентациях), долю экрана, занимаемую кадром в полноэкранном режиме, стоп-кадр, работу без подключённого модуля с переподключением на ходу и переключение языка в окне.
Любительский проект, не аффилирован с UNI-T. Приложение только читает то, что отдаёт модуль, и не является измерительным прибором: показания зависят от коэффициента излучения поверхности, расстояния и прогрева камеры. Для ответственных измерений нужен поверенный прибор.
GNU GPL v3 © 2026 Alexey Verhogladov.
Проект бесплатный и сделан на энтузиазме. Если он пригодился - спасибо за поддержку (USDT):
| Сеть | Адрес |
|---|---|
| USDT · TRC20 | TYLmBvdxL8t9ziyZiFp3jcHwQbAsZR5haZ |
| USDT · TON | UQCnVND-uBgkWIAD1UP14tsN8239KE5BTOfKSJmg-0XqrN-k |
| USDT · ERC20 | 0xa61dcA98A86D84883Ddb3d62aA7F008f157c8eFF |

