В общем, удалось запустить и связать ИБП c виртуальным устройством WB , делюсь инструкцией и скриптами, вдруг , кому пригодится.
1. Раздобыть или спаять кабель по схеме вашей модели, самый распространенный (и подходит на всю серию APC SURT) 940-024C:
2. Настроить Docker (если не настроен уже)
3. Создать (где удобно) директорию и файл конфигурации docker-compose.yml (от root пользователя):
docker-compose.yml
services:
app:
image: ${REGISTRY_URI:-instantlinux}/nut-upsd:latest
restart: always
environment:
API_USER: apcmon #произвольный пользователь к API
API_PASSWORD: apcmon #произвольный пароль к API
DRIVER: apcsmart #ваш драйвер ИБП из https://networkupstools.org/stable-hcl.html
NAME: APC #произвольное имя устройства
PORT: /dev/ttyUSB0 #ваш порт! Сразу при подключении USB можно глянуть dmseg | grep tty
SDORDER: -1 #отключение автовыключения ИБП
ports:
- ${PORT_UPSD_1:-3493}:3493
privileged: true
nut:
image: 2mqtt/nut:0.0.3
restart: always
environment:
- MQTT_ID=nut
- MQTT_PATH=nut #название раздела в MQTT, куда будет публикация
- MQTT_HOST=mqtt://192.168.2.54 #IP адрес MQTT сервиса, совпадает с адресом контроллера WB
- MQTT_USERNAME=mqttuser #пользователь для авторизации в MQTT, если настроена авторизация в mosquitto
- MQTT_PASSWORD=mqttuserpwd #пароль для авторизации в MQTT, если настроена авторизация в mosquitto
- NUT_HOST=192.168.2.54 #IP адрес сервера, на котором развернут NUT, т.е. локальный адрес данного сервера
- NUT_USERNAME=apcmon #должен соответствовать пользователю NUT API_USER
- NUT_PASSWORD=apcmon #должен соответствовать паролю NUT API_PASSWORD
- NUT_INTERVAL=60000 # интервал опроса в мс
5. Поднять контейнеры:
docker compose up -d --build
6. Есть нюанс, связанный с тем, что nut2mqtt модуль написан криво, и если его поднимать одновременно с Nut - работать не будет, поэтому надо после поднятия (и при каждой перезагрузке) рестартануть контейнер nut2mqtt:
docker restart nut-nut-1
И вставить это в cron (crontab -e), чтобы срабатывало при перезагрузке:
@reboot /bin/sleep 60 ; sudo docker restart nut-nut-1
7. Настроить конвертер в виртуальное устройство WB
Заходим в настройку правил и добавляем новый файл, process_apc_mqtt.js:
var devName = "APC";
var mqttBase = "nut/APC/";
var regBattery = "battery";
var regInput = "input";
var regOutput = "output";
var regStatus = "status";
defineVirtualDevice(devName,
{title: "Smart-UPS RT 2000 XL",
cells: {
"input.frequency": {type: "value", "units": "Hz", "title": "Линия.Частота", value: -1, "order": 1},
"input.quality": {type: "text", "title": "Линия.Качество", value: 'Н/Д', "order": 2},
"input.sensitivity": {type: "text", "title": "Линия.Чувствительность", value: "Н/Д", "order": 3},
"input.transferHigh": {type: "value", "units": "V", "title": "Линия.Высокое напряжение переключения", value: -1, "order": 4},
"input.transferLow": {type: "value", "units": "V", "title": "Линия.Низкое напряжение переключения", value: -1, "order": 5},
"input.transferReason": {type: "text", "title": "Линия.Причина переключения", value: "Н/Д", "order": 6},
"input.voltage": {type: "value", "units": "V", "title": "Линия.Напряжение", value: -1, "order": 7},
"input.voltageMaximum": {type: "value", "units": "V", "title": "Линия.Макс напряжение", value: -1, "order": 8},
"input.voltageMinimum": {type: "value", "units": "V", "title": "Линия.Мин напряжение", value: -1, "order": 9},
"battery.alarmThreshold": {type: "value", "units": "min", "title": "Батарея.Тревога по оставшимся минутам", value: -1, "order": 10},
"battery.charge": {type: "value", "units": "%", "title": "Батарея.Заряд", value: -1, "order": 11},
"battery.chargeRestart": {type: "value", "units": "%", "title": "Батарея.Мин процент заряда для включения", value: -1, "order": 12},
"battery.date": {type: "text", "title": "Батарея.Дата замены", value: "Н/Д", "order": 13},
"battery.packs": {type: "value", "title": "Батарея.Количество комплектов", value: -1, "order": 14},
"battery.packsBad": {type: "value", "title": "Батарея.Количество плохих комплектов", value: -1, "order": 15},
"battery.runtime": {type: "value", "units": "s", "title": "Батарея.Запас времени", value: -5, "order": 16},
"battery.runtimeLow": {type: "value", "units": "s", "title": "Батарея.Отключение при запасе", value: -1, "order": 17},
"battery.voltage": {type: "value", "units": "V", "title": "Батарея.Напряжение", value: -1, "order": 18},
"battery.voltageNominal": {type: "value", "units": "V", "title": "Батарея.Номинал напряжения", value: -1, "order": 19},
"output.voltage": {type: "value", "units": "V", "title": "Выход.Напряжение", value: -1, "order": 20},
"output.voltageNominal": {type: "value", "units": "V", "title": "Выход.Номинал напряжения", value: -1, "order": 21},
"status.mode": {type: "text", "title": "Статус.Режим", value: -1, "order": 22},
"status.beeper": {type: "switch", "title": "Статус.Сигнал", value: false, "order": 23},
"status.temperature": {type: "value", "units": "deg C", "title": "Статус.Температура", value: -1, "order": 24},
"status.load": {type: "value", "units": "%", "title": "Статус.Нагрузка", value: -1, "order": 25}
}
});
function setDevRegister(message) {
//log.info("name: {}, value: {}".format(message.topic, message.value))
regBase = /[^/]*$/.exec(message.topic)[0]+'.';
if (message.value != '') {
JSON.parse(message.value, function(n, p) {
curVal = dev[devName+'/'+regBase+n];
curValType = typeof(curVal);
if ((n) && (curValType !== 'undefined')) {
var v;
switch(curValType) {
case "number": v = parseInt(p); break;
case "float": v = parseFloat(p); break;
case "string": v = p; break;
case "boolean": v = (p === 'true'); break;
default: log('unknown curValType = ' + curValType); v = p;
}
dev[devName+'/'+regBase+n] = v;
}
});
}
}
trackMqtt(mqttBase + regInput, setDevRegister);
trackMqtt(mqttBase + regBattery, setDevRegister);
trackMqtt(mqttBase + regOutput, setDevRegister);
trackMqtt(mqttBase + regStatus, setDevRegister);
Вуаля, получаем связь: