Сегодня я хочу поделится своим небольшим исследованием.
Дано
1. UPS dexp 1500VA USB, купленный в надежде на работу через nut.
в системе виден как
ID 0001:0000 Fry's Electronics
2. Linux debian
Попытка настройки nut не увенчалась успехом,
UPS упорно не хотел возвращать реальные значения напряжений, возвращая вместо этого нули.
Итоговый конфиг /etc/nut/ups.conf ниже
[dexp] vendorid = 0001 productid = 0000 desc = "DEXP 1500VA USB" driver = blazer_usb subdriver = krauler langid_fix = 0x0409 port = /dev/usbhid-ups
На запрос в тех-поддержку dexp был получен ответ, о том, что linux не входит в список поддерживаемых систем. Ну ок, не входит так не входит.
Было принято решение провести реверс-инженеринг протокола обмена.
Для этого быстренько развернул windows xp под qemu, и на ней установил софт, идущий в комплекте с этим ИБП.
О том, как собрать дамп обмена проприетарной программы и UPS DEXP 1500VA по USB шине
Чтобы собрать дамп, потребуется 3 вещи, подгрузить модуль usbmon, смонтировать debugfs, и собственно, сам tcpdump.
modprobe usbmon mount -t debugfs none_debugs /sys/kernel/debug tcpdump -i usbmon0 -w usb-dump.ws
В открытом wireshark-ом собранном дамп(usb-dump.ws) был четко виден набор комманд, посредством которых обеспечивалось взаимодействие контроллера ups dexp и usb хоста.
Получение данных с помощью python
Дабы убедится, что все понято правильно, я написал небольшую программку на python, реализующую полученную информацию
#!/usr/bin/env python # -*- coding: utf-8 -*- import usb.core from usb.control import get_descriptor # find our device dev = usb.core.find(idVendor=0x0001, idProduct=0x0000) if dev is None: raise ValueError('Device not found') def get_idx(dev, idx, size): DESC_TYPE_STRING = 0x03 buf = get_descriptor(dev, size, DESC_TYPE_STRING, idx, 0x0409) return buf[2:buf[0]].tostring().decode('utf-16-le') # MEC0003 print get_idx(dev, 1, 1026) # status (Q1)03 (226.0 000.0 226.0 000 50.0 27.0 29.0 00001001 print get_idx(dev, 3, 102) # stat2 (F)0d #220.0 4.1 24.00 50.0 print get_idx(dev, 13, 102) # (?)f3 \005 print get_idx(dev, 243, 102) # name (I)0c # 1500VA V2.2 print get_idx(dev, 12, 102) # status (Q1)03 (226.0 000.0 226.0 000 50.0 27.0 29.0 00001001 print get_idx(dev, 3, 102)
Следующим шагом стало развертывание исходников nut, в моем случае это была версия nut-2.6.4, создание патча к драйверу blazer_usb, и сборка пакета debian.
Патч blazer_usb_dexp_fix.patch
--- a/drivers/blazer_usb.c 2017-10-16 19:52:58.151296950 +0500 +++ b/drivers/blazer_usb.c 2017-10-16 19:52:40.491296378 +0500 @@ -239,6 +239,9 @@ upsdebugx(3, "send: %.*s", (int)strcspn(cmd, "\r"), cmd); + /* SPEEDHACK */ + buflen = 102; + for (i = 0; command[i].str; i++) { int retry;
Не вдаваясь в особенности коммуникации по USB скажу суть — проблема оказалась в контроллере ups dexp, который не отвечает на запрос, с анонсированной длинной буфера 512 байт, а отвечает только, если она (длинна) установлена равной в 102 байта. Для чего это было сделано, и каковы внутренние причины — мне понять не удалось.
Как видно из патча, для комманд krauler мы сознательно устанавливаем длину буффера, равной 102 байта.
Процесс сборки
1. Подключаем исходники в /etc/apt/sources.list
deb-src http://ftp.debian.org/debian wheezy main contrib non-free deb-src http://security.debian.org/ wheezy/updates main contrib non-free deb-src http://mirror.yandex.ru/debian/ wheezy-backports main contrib non-free
2. Ставим все необходимое для сборки
apt-get install build-essential fakeroot devscripts
3. Устанавливаем все зависимые пакеты, собираем и устанавливаем nut-server
apt-get update apt-get source nut-server apt-get build-dep nut-server
Накладываем наш patch и собраем пакет
apt-get install quilt cd nut-2.6.4 quilt import blazer_usb_dexp_fix.patch && quilt push dch -n debuild -b -uc -us dpkg -i ../nut-server_2.6.4-2.3+deb7u1_amd64.deb
финал
Перезапускаем nut
/etc/init.d/nut-server restart
и проверяем ответ командой upsc dexp
battery.voltage: 27.00 battery.voltage.high: 0.00 battery.voltage.low: 0.00 battery.voltage.nominal: 0.0 device.mfr: device.model: device.type: ups driver.name: blazer_usb driver.parameter.langid_fix: 0x0409 driver.parameter.pollinterval: 2 driver.parameter.port: /dev/usbhid-ups driver.parameter.productid: 0000 driver.parameter.subdriver: krauler driver.parameter.vendorid: 0001 driver.version: 2.6.4 driver.version.internal: 0.08 input.current.nominal: 0.0 input.frequency: 50.0 input.frequency.nominal: 0 input.voltage: 229.0 input.voltage.fault: 0.0 input.voltage.nominal: 0 output.voltage: 231.0 ups.beeper.status: enabled ups.delay.shutdown: 30 ups.delay.start: 180 ups.firmware: ups.load: 15 ups.mfr: ups.model: ups.productid: 0000 ups.status: OL ups.temperature: 29.0 ups.type: offline / line interactive ups.vendorid: 0001