Как я разработал на Go демон для управления экраном кулера Ocypus в Linux
Всем привет!
Моя основная операционная система — Linux (Arch). Она меня полностью устраивает, но недавно я обновил процессор и заодно решил заменить кулер. Выбор остановился на Ocypus Delta A62 EX Digital. У него есть простая, но приятная особенность: небольшой дисплей, на который можно вывести температуру процессора или видеокарты.
У Ocypus есть официальное приложение для Windows, а вот версии для Linux нет. Поэтому я решил создать собственную. В тот период я как раз изучал Go: язык почти идеально подходил для этой задачи, а сам проект стал хорошим поводом попрактиковаться. Возможно, когда-нибудь я перепишу его на C или Rust, но из-за учёбы пока не нахожу на это времени.
Так появился проект ocyd.
-
Репозиторий: github.com/Dilikel/ocyd
-
Лицензия: GPLv3
От идеи до работающего демона
1. Как система видит дисплей
Для начала нужно было выяснить, как кулер определяется в системе. Ответ нашёлся быстро: дисплей отображается как 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
Похоже, Ocypus использовали готовую платформу от игровой клавиатуры, чтобы не тратиться на собственные VID и PID.
2. Изучение протокола
Следующим шагом было разобраться, какие данные передаёт официальное приложение и как они изменяются. Для этого я воспользовался 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. Демон открывает устройство по VID:PID и каждую секунду отправляет ему актуальное значение температуры.
3. Разработка демона
Разобравшись с форматом данных, я приступил к самому демону. Как и в официальном приложении, пользователю доступны следующие настройки:
-
источник температуры: CPU или GPU;
-
единицы измерения: градусы Цельсия или Фаренгейта;
-
собственные VID:PID устройства.
Последний пункт особенно важен: я могу гарантировать работу только со своей версией кулера. Другие модели могут как поддерживаться, так и нет. К тому же жёстко прописывать VID:PID в коде — не лучшая практика.
4. Как получить температуру в Linux
В 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. Сначала проверяется sysfs, и только если подходящий датчик там не найден, используется NVML.
Подробно разбирать реализацию не буду: исходный код открыт, поэтому его можно изучить и подсказать мне, если что-то сделано неправильно.
5. Доступ к устройству без root: udev
Чтобы демон мог записывать данные в 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" предоставляет доступ к узлу устройства всем локальным пользователям. Для дисплея, который всего лишь показывает число, я посчитал это допустимым.
6. systemd, конфигурация и ocydctl
Демон работает как пользовательский сервис 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 можно указывать как в шестнадцатеричном, так и в десятичном формате. Демон считывает настройки только при запуске, поэтому после их изменения потребуется перезапуск.
7. Установка и удаление одной командой
Чтобы пользователю не приходилось вручную размещать файлы, я написал скрипты install.sh и uninstall.sh.
-
ищет готовые бинарные файлы: сначала рядом со скриптом, если скачан архив релиза, затем в
bin/, если склонирован репозиторий. Если файлов нет, собирает их самостоятельно; -
предлагает сохранить VID:PID по умолчанию (
1a2c:434d, Delta A62 EX Digital) или указать свои; -
копирует бинарные файлы в
/usr/bin, а правило udev — в/etc/udev/rules.d; -
создаёт конфигурационный файл, если он ещё отсутствует;
-
спрашивает, нужен ли systemd. Если нужен, размещает сервис в
~/.config/systemd/user/и предлагает включить автозапуск и сразу запустить его.
uninstall.sh выполняет обратные действия: останавливает и отключает сервис, удаляет юнит, бинарные файлы и правило 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и введите свои значения; -
создайте issue, даже если устройство не заработало: укажите точную модель, дистрибутив, VID:PID и логи
journalctl --user -u ocyd.service.
Также буду признателен за code review. Если видите, что можно улучшить, напишите в комментариях или создайте issue.
Спасибо за внимание!
У межзвёздной кометы 3I/ATLAS обнаружили уникальную световую подпись: её пыль почти целиком состоит из необычного вещества
Сдвиги вместо MUL на 8088: по таблице выигрывают все константы, с учётом выборки байтов — только 9–21%
Самолёт COMAC CE-25A совершил первый пилотируемый полёт
Скоро выйдет компактный смартфон с 6,3-дюймовым экраном и технологиями LTPO/LIPO
«Спутникс» работает над созданием лазерной связи
С Восточного запустили «Союз-2.1б» с частными спутниками в рамках первого субсидированного пуска по нацпроекту «Космос»
Starship S42 вывел на орбиту все 26 спутников Starlink V3 и теперь облетает Землю со скоростью 26 тыс. км/ч
АЭС «Аккую», которую «Росатом» строит в Турции, готовят к горячей обкатке