Как я разработал на Go демон для управления экраном кулера Ocypus в Linux

Прокомментировать Просмотры: 7

Всем привет!

Моя основная операционная система — Linux (Arch). Она меня полностью устраивает, но недавно я обновил процессор и заодно решил заменить кулер. Выбор остановился на Ocypus Delta A62 EX Digital. У него есть простая, но приятная особенность: небольшой дисплей, на который можно вывести температуру процессора или видеокарты.

У Ocypus есть официальное приложение для Windows, а вот версии для Linux нет. Поэтому я решил создать собственную. В тот период я как раз изучал Go: язык почти идеально подходил для этой задачи, а сам проект стал хорошим поводом попрактиковаться. Возможно, когда-нибудь я перепишу его на C или Rust, но из-за учёбы пока не нахожу на это времени.

Так появился проект ocyd.

От идеи до работающего демона

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 байт:

Байт (нумерация с нуля)

Значение

0

Report ID = 0x07

1, 2

0xFF, 0xFF

3, 4, 5

цифры температуры: сотни, десятки и единицы

6—63

нули

Температура передаётся самым прямолинейным способом: не одним числом, а тремя отдельными цифрами. На этом исследование протокола и закончилось — ничего сложнее не понадобилось 🙂

Отправка в коде выглядит так:

// 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

k10temp, coretemp, zenpower, amd_energy

Tctl, Tdie, Package id 0, Package id 1

GPU

amdgpu, nouveau, nvidia, i915, xe

edge, junction, hotspot, 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.

install.sh:

  1. ищет готовые бинарные файлы: сначала рядом со скриптом, если скачан архив релиза, затем в bin/, если склонирован репозиторий. Если файлов нет, собирает их самостоятельно;

  2. предлагает сохранить VID:PID по умолчанию (1a2c:434d, Delta A62 EX Digital) или указать свои;

  3. копирует бинарные файлы в /usr/bin, а правило udev — в /etc/udev/rules.d;

  4. создаёт конфигурационный файл, если он ещё отсутствует;

  5. спрашивает, нужен ли 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 или вы заметили ошибку в проекте, буду благодарен за обратную связь:

  1. определите VID:PID с помощью команды lsusb;

  2. укажите их в конфигурации и правиле udev либо просто запустите ./install.sh и введите свои значения;

  3. создайте issue, даже если устройство не заработало: укажите точную модель, дистрибутив, VID:PID и логи journalctl --user -u ocyd.service.

Также буду признателен за code review. Если видите, что можно улучшить, напишите в комментариях или создайте issue.

Спасибо за внимание!

 

Источник

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов