- PVSM.RU - https://www.pvsm.ru -
Всем привет!
Моя основная система — Linux (Arch). Мне она очень нравится, но относительно недавно я обновил процессор и решил заодно поменять кулер. Выбор пал на Ocypus Delta A62 EX Digital. У него есть простая, но симпатичная фича: небольшой экранчик, на котором можно показывать температуру процессора или видеокарты.
У Ocypus есть официальное приложение для Windows, а для Linux — нет. Поэтому я решил написать своё. В то время я как раз изучал Go, и он почти идеально подходит под такую задачу, но, честно говоря, это было ещё и желанием потренироваться в языке. К слову, я думаю когда‑нибудь переписать всё на C или Rust, но из‑за большого объёма учёбы времени на это пока нет.
Так и начался проект ocyd.
Репозиторий: github.com/Dilikel/ocyd [1]
Лицензия: GPLv3
Первым делом нужно было понять, как кулер выглядит для системы. Выяснилось быстро: экран определяется как HID‑устройство с VID:PID = 1a2c:434d и названием «China Resource Semico Co., Ltd USB Gaming Keyboard».
Bus 001 Device 005: ID 1a2c:434d China Resource Semico Co., Ltd USB Gaming Keyboard
Видимо, чтобы не тратить деньги на собственные VID и PID, Ocypus просто взяли чужую платформу от какой‑то игровой клавиатуры.
Дальше нужно было понять, какие данные отправляет официальное приложение и как они меняются. Для этого я использовал Wireshark и драйвер USBPcap на Windows. Зная VID:PID, достаточно легко отфильтровать трафик именно этого устройства.
Проанализировав байты, я понял, что официальное приложение отправляет пакет из 64 байт:
|
Байт (с нуля) |
Значение |
|---|---|
|
|
Report ID = |
|
|
|
|
|
цифры температуры: сотни, десятки, единицы |
|
|
нули |
То есть температура отправляется очень «в лоб»: не одним числом, а тремя отдельными цифрами. На этом весь реверс‑инжиниринг, больше ничего и не потребовалось:)
В коде отправка выглядит так:
// SendTemp раскладывает температуру на 3 цифры и пишет 64-байтный репорт.
func (d *Display) SendTemp(temp int) error {
digits := decompose(temp)
copy(d.buf[3:6], digits[:])
n, err := d.dev.Write(d.buf[:])
// ...
}
func decompose(temp int) [3]byte {
if temp < 0 {
temp = 0
}
if temp > 999 {
temp = 999
}
return [3]byte{
byte(temp / 100),
byte((temp % 100) / 10),
byte(temp % 10),
}
}
Для работы с HID я взял библиотеку sstallion/go-hid [2]. Демон открывает устройство по VID:PID и раз в секунду шлёт актуальную температуру.
Зная, что отправлять, осталось написать, собственно, демон. Как и в официальном приложении, я решил дать пользователю выбор:
температура CPU или GPU;
градусы Цельсия или Фаренгейта;
собственный VID:PID устройства.
Последний пункт важен. Я могу ручаться только за свою версию кулера: другие модели, возможно, работают, возможно, нет. А хардкодить VID:PID вообще плохая идея.
Температура процессора в Linux лежит в /sys/class/hwmon/hwmon*/temp*_input. Осталось понять, чем именно являются эти * в каждом конкретном случае. Для этого я описал базовые драйверы и метки их датчиков, по которым демон находит нужный файл:
|
Источник |
Драйверы |
Метки |
|---|---|---|
|
CPU |
|
|
|
GPU |
|
|
Алгоритм простой: перебираем hwmon*/name, ищем драйвер из списка, внутри него ищем temp*_label с подходящей меткой и читаем соответствующий temp*_input. Если метки не нашлось, берём temp1_input. Ядро отдаёт значения в милли‑градусах Цельсия, поэтому результат делится на 1000.
cpuTarget = sensorTarget{
Drivers: []string{"k10temp", "coretemp", "zenpower", "amd_energy"},
Labels: []string{"Tctl", "Tdie", "Package id 0", "Package id 1"},
}
Для видеокарт всё то же самое, кроме проприетарного драйвера NVIDIA: он не отдаёт датчик через hwmon. Для него демон использует библиотеку NVML (через go-nvml [3]): сначала пробуется sysfs, и только если там ничего не нашлось, включается NVML.
Не буду углубляться в реализацию: весь код открыт, вы можете его посмотреть, разобраться и подсказать мне, если я что‑то делаю неправильно.
Чтобы демон мог писать в HID‑устройство, нужно правило udev. Без него hidraw‑узел принадлежит root, а запускать демон от root не хочется.
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1a2c", ATTRS{idProduct}=="434d", MODE="0666"
Здесь 1a2c:434d — это VID:PID именно Ocypus Delta A62 EX Digital. У других моделей он может быть другим, поэтому его можно поменять и в правиле, и в конфиге. Стоит помнить и о компромиссе: MODE="0666" открывает узел этого устройства всем локальным пользователям. Для экрана, который просто показывает число, я счёл это приемлемым.
Демон запускается как пользовательский systemd‑сервис, так что root для него не нужен:
systemctl --user enable --now ocyd.service
journalctl --user -u ocyd.service -f
Настройки лежат в ~/.config/ocyd/config.toml:
[display]
source = 'CPU' # 'CPU' или 'GPU'
unit = 'C' # 'C' или 'F'
[device]
vendor_id = '0x1a2c'
product_id = '0x434d'
Чтобы не править файл руками, я сделал небольшую утилиту ocydctl:
ocydctl -source GPU -unit F
ocydctl -vendor 0x1a2c -product 0x434d
systemctl --user restart ocyd.service
VID и PID принимаются и в hex, и в десятичном виде. Демон читает конфиг только при старте, поэтому после изменений его нужно перезапустить.
Чтобы человеку не приходилось руками раскладывать файлы, я написал install.sh [4] и uninstall.sh [5].
install.sh [4]:
ищет готовые бинарники: сначала рядом со скриптом (если вы скачали архив релиза), затем в bin/ (если склонировали репозиторий). Если их нет, соберёт сам;
спрашивает, оставить ли VID:PID по умолчанию (1a2c:434d, Delta A62 EX Digital) или ввести свои;
копирует бинарники в /usr/bin, а udev‑правило в /etc/udev/rules.d;
создаёт конфиг, если его ещё нет;
спрашивает, нужен ли systemd. Если да, кладёт сервис в ~/.config/systemd/user/ и спрашивает, включить ли автозапуск и запустить ли сразу.
uninstall.sh [5] делает обратное: останавливает и отключает сервис, удаляет юнит, бинарники и udev‑правило, а про конфиг спрашивает отдельно.
Из архива релиза (сборка не нужна):
tar -xzf ocyd_*_linux_amd64.tar.gz
cd ocyd_*_linux_amd64
./install.sh
Из исходников:
git clone https://github.com/Dilikel/ocyd.git
cd ocyd
./install.sh
Для сборки понадобятся Go, gcc и заголовки libudev (libudev-dev в Debian/Ubuntu, systemd-libs в Arch).
На данный момент я реализовал:
демон, который каждую секунду отправляет температуру CPU или GPU на экран кулера и при этом уже сейчас потребляет всего около 10 МБ оперативной памяти;
поддержку датчиков AMD, Intel и NVIDIA, включая проприетарный драйвер через NVML;
выбор источника и единиц измерения (°C / °F) и настраиваемые VID:PID;
утилиту ocydctl для изменения настроек из командной строки;
пользовательский systemd‑сервис, udev‑правило и скрипты установки и удаления;
готовые релизы через GoReleaser и двуязычный README (русский и английский).
Важная оговорка: проект на ранней альфа‑стадии. Проверено только на моём кулере, Ocypus Delta A62 EX Digital. Юнит‑тестов пока нет, а код ещё не оптимизирован и не отполирован.
Что планирую дальше:
добавить тесты (конфиг, поиск датчиков на поддельном дереве sysfs, HID‑драйвер на мок‑устройстве);
сделать код чище и лучше: рефакторинг, аккуратная обработка ошибок и структура;
выпустить пакеты в AUR: ocyd (сборка из исходников) и ocyd-bin (готовый релиз);
поддержать больше моделей кулеров.
Если у вас другой кулер Ocypus (или вы просто нашли у меня ошибку), буду рад любой обратной связи:
узнайте VID:PID командой lsusb;
поменяйте их в конфиге и в udev‑правиле (или просто запустите ./install.sh [4] и введите свои значения);
напишите в issue [6], даже если не заработало: укажите точную модель, дистрибутив, VID:PID и логи journalctl --user -u ocyd.service.
Также буду благодарен за code review: если вы видите, как сделать лучше, пишите в комментариях или в issue.
Спасибо, что дочитали!
Автор: Dilikel
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/462217
Ссылки в тексте:
[1] github.com/Dilikel/ocyd: http://github.com/Dilikel/ocyd
[2] sstallion/go-hid: https://github.com/sstallion/go-hid
[3] go-nvml: https://github.com/NVIDIA/go-nvml
[4] install.sh: http://install.sh
[5] uninstall.sh: http://uninstall.sh
[6] issue: https://github.com/Dilikel/ocyd/issues/new/choose
[7] Источник: https://habr.com/ru/articles/1107776/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1107776
Нажмите здесь для печати.