Перейти к содержанию

Рекомендуемые сообщения

  • 6 месяцев спустя...
Опубликовано

Всем привет! Сейчас занимаюсь настройкой NUT на Viva KN-1910. В целом всё завелось кроме веба. У меня и на nginx, и на apache при попытки открыть страницу с CGI - автоматически сохраняются файлы. Как я понял - это скрипты и с ними nginx не работает. Поэтому я поставил apache по инструкции ниже, но проблема сохранилась.

Что касается инструкции - я готов выложить после того как настрою пошаговую инструкцию.

Через консоль управлять ИБП получается, так же поставил клиентскую чать WinNUT - https://github.com/gawindx/WinNUT-Client/releases/tag/2.1.7740.35837/ Через это ПО коннект успешный, автозапуск файлов NUT после рестарта роутера работает.

Буду очень благодарен и признателен за помощь!

  • 2 недели спустя...
Опубликовано

Здравствуйте! 

Можете написать инструкцию по установке-настройке без веба? Мне бы хватило работы с клиентской часть или консолью. Спасибо!

  • 1 месяц спустя...
Опубликовано
В 25.03.2025 в 23:19, KyZZMI4 сказал:

Здравствуйте! 

Можете написать инструкцию по установке-настройке без веба? Мне бы хватило работы с клиентской часть или консолью. Спасибо!

День добрый! Да - готов написать. Только у меня так веб и не заработал :( Пробовал разные веб-сервера настроить, данные не отображаются. Подготовлю инструкцию завтра и выложу.

Опубликовано
В 25.03.2025 в 23:19, KyZZMI4 сказал:

Здравствуйте! 

Можете написать инструкцию по установке-настройке без веба? Мне бы хватило работы с клиентской часть или консолью. Спасибо!

Добрый день! Извиняюсь за долгий ответ. Итак:
Мной были установлены комоненты NUC как это было написано в самом начале данной темы. Модель моего UPS - CPS Value400EI. Драйвер использовал blazer_ser. - только с ним заработало. На сайте проекта можно посмотреть какой драйвер будет поддерживаться для определённой модели - https://networkupstools.org/stable-hcl.html Моя модель была указана без буквы I (как я понял - это просто модификация). Теперь что касается настроек конфигов (комментарии я не удалял, только либо раскомментировал, либо добавлял строки. Для удобства можно сделать бэкапы). Для редактирования я использовал winSCP и notepad++. Настроил в winSCP открытие текстовых файлов через NP++:
/opt/etc/init.d/S15upsd 

Спойлер

#!/bin/sh

ENABLED=yes
PROCS=upsd
ARGS="-u root"
PREARGS=""
DESC=$PROCS
PATH=/opt/sbin:/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

. /opt/etc/init.d/rc.func

hosts.config

Спойлер

MONITOR myups@localhost "CPS Value400EI"

nut.config

Спойлер

MODE=netserver

ups.config (номера vendorid и productid надо посмотреть какие в системе отобразились, могут откличатсья)

Спойлер

[myups]
    driver = usbhid-ups 
    port = auto
    desc = "CPS Value400EI"  
    vendorid = 0764
    productid = 0501

upsd.users

Спойлер

[upsadmin]
        password = пароль
        actions = SET
        actions = FSD
        instcmds = ALL
        upsmon master
    
    [upsuser]
        password = пароль
        upsmon slave    

    [upsmonitor]
        password  = пароль
        upsmon primary


Теперь в консоль (например через putty) добавляем в автозапуск S15upsd и запускам через команду upsd. У меня сейчас вот так:

Спойлер

Network UPS Tools upsd 2.8.1
Fatal error: A previous upsd instance is already running!

Используя команду upsc myups@localhost можно увидеть список всех данных бесперебойника и управлять, например, сигналом. Который можно выключить:

Спойлер

ups.beeper.status: disabled

Для подключения к UPS из Windows настроил WinNUT-client. Нужно обратить внимание, что после настройки соединения нужно перезапусить программу, иначе не будет отображать, что коннект успешно установлен. На этом всё. Будут вопросы - пишите!

  • 5 месяцев спустя...
Опубликовано (изменено)

У меня  получилось подключить веб используя веб-сервер lighttpd (помог настроить ИИ), но вот вкладка "Settings" так и не завелась. 
Так же я настроил телегам бота на роутере для отправки статусов по заряду и "в сети" себе + установил mosquitto-client-nossl  для записи в mqtt топик данных по батарее, и для дальнейшего прокидывания данных на mqtt сервер умного дома Sprut Hub (был написан кастомный шаблон). В целом по данному методу можно прокинуть и в Home Assistance.
Начнём по порядку:
Телеграм-бот

Я опубликовал бота по пути etc/telegram_bot, подключил виртуальное окружение venv (через virtualenv), секреты прописал в .env. Архив с ботом приложил.
.env

python3 -m pip install virtualenv

 

Спойлер

ALLOWED_USERS="тут_указать_user_id либо группу через запятую"
TELEGRAM_BOT_TOKEN="token id"
UPS_NAME="myups@localhost" #имя ups из конфига nut
CHECK_INTERVAL="60" # 1 минута
LOW_BATTERY_THRESHOLD="20" # низкий уровень заряда аккумулятора ups
LOG_FILE="/opt/etc/telegram_bot/bot.log" #путь до логов

bot.py (в коде есть квадратик с вопросом - на самом деле это эмоджи батарейки, не удаляйте :) )

Спойлер
import os
import asyncio
import subprocess
import time
from dotenv import load_dotenv
from telegram import Bot
from telegram.request import HTTPXRequest

# Конфигурация
load_dotenv()
TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
ALLOWED_USERS = {int(id) for id in os.getenv("ALLOWED_USERS").split(",") if id}
UPS_NAME = os.getenv("UPS_NAME", "ups@localhost")
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "60"))
LOW_BATTERY_THRESHOLD = int(os.getenv("LOW_BATTERY_THRESHOLD", "10"))
LOG_FILE = os.getenv("LOG_FILE", "/opt/etc/telegram_bot/bot.log")

# Инициализация бота
bot = Bot(
    token=TOKEN,
    request=HTTPXRequest(
        connection_pool_size=1,
        read_timeout=10,
        write_timeout=10,
        connect_timeout=10
    )
)

# Глобальные переменные
last_messages = {}
send_lock = asyncio.Lock()
critical_notification_sent = False


def write_log(message: str):
    """Логирование с временной меткой"""
    try:
        os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {message}\n")
    except Exception as e:
        print(f"Ошибка записи лога: {e}")


async def parse_ups_status():
    """Парсинг статусов ИБП"""
    try:
        result = subprocess.run(
            ["upsc", UPS_NAME],
            capture_output=True,
            text=True,
            timeout=5
        )

        if result.returncode != 0:
            write_log(f"Ошибка upsc: {result.stderr.strip()}")
            return {"status": "ERROR", "message": "🐞 Ошибка запроса к ИБП"}

        data = {}
        charge = None
        status_flags = []

        for line in result.stdout.splitlines():
            if ":" in line:
                key, val = line.split(":", 1)
                key = key.strip()
                val = val.strip()
                data[key] = val

                if key == "ups.status":
                    status_flags = val.split()
                elif key == "battery.charge":
                    try:
                        charge = int(val)
                    except ValueError:
                        charge = 0

        # Анализ статуса
        is_online = "OL" in status_flags
        is_charging = "CHRG" in status_flags
        is_low_batt = "LB" in status_flags
        is_fsd = "FSD" in status_flags
        is_on_battery = "OB" in status_flags
        is_discharging = "DISCHRG" in status_flags

        # OB DISCHRG LB

        # Определение статуса
        # 1. Проверяем заряд в первую очередь
        if is_charging:  
            if is_low_batt:
                status = "CHARGING_LOW"
                message = f"🔌 ИБП в сети, батарея заряжается: 🪫 {charge}%"
            else:
                status = "CHARGING"
                message = f"🔌 ИБП в сети, батарея заряжается: 🔋 {charge}%"
        # 2. Режим работы от батареи
        elif is_on_battery:
            if is_low_batt and charge <= LOW_BATTERY_THRESHOLD:
                status = "FSD_CRITICAL"
                message = f"🚨 ИБП критический 🪫: {charge}% ≤ {LOW_BATTERY_THRESHOLD}%"
            elif is_low_batt:
                status = "ONBATTERY_LOW"
                message = f"⚠️ ИБП не в сети, 🪫 {charge}%"
            else:
                status = "ONBATTERY"
                message = f"⚠️ ИБП не в сети, 🔋 {charge}%"
        # 3. Онлайн (без зарядя)
        elif is_online:
            status = "ONLINE"
            message = f"🔌 ИБП в сети, 🔋 {charge}%"
        # 4. Все остальные случаи
        else:
            status = "UNKNOWN"
            message = f"⁉️ ИБП Неизвестный статус: {' '.join(status_flags)}"

        return {
            "status": status,
            "charge": charge,
            "flags": status_flags,
            "message": message,
            "is_critical": status in ("FSD_CRITICAL", "ONBATTERY_LOW")
        }

    except subprocess.TimeoutExpired:
        return {"status": "TIMEOUT", "message": " ИБП не отвечает"}
    except Exception as e:
        write_log(f"Ошибка парсинга статуса: {e}")
        return {"status": "ERROR", "message": f" Ошибка мониторинга: {str(e)}"}


async def send_alert(chat_id: int, message: str, cooldown: int = 30) -> bool:
    """Отправка сообщения с защитой от дублей"""
    global last_messages

    async with send_lock:
        now = time.time()
        last_msg, last_time = last_messages.get(chat_id, (None, 0))

        if message == last_msg and now < last_time + cooldown:
            # write_log(f"Попытка дублирования (заблокировано): chat_id={chat_id}")
            return False

        for attempt in range(3):
            try:
                await bot.send_message(chat_id=chat_id, text=message)
                last_messages[chat_id] = (message, now)
                # write_log(f"Уведомление отправлено в {chat_id}")
                return True
            except Exception as e:
                if attempt == 2:
                    write_log(f"Ошибка отправки в {chat_id}: {e}")
                await asyncio.sleep(2)
        return False


async def monitor_ups():
    """Основной цикл мониторинга"""
    global critical_notification_sent

    write_log("===== Бот запущен =====")

    last_status = None
    try:
        while True:
            ups_data = await parse_ups_status()
            current_status = ups_data["status"]

            # Логирование только при изменении
            if current_status != last_status:
                status_info = f"Статус: {current_status} | Заряд: {ups_data.get('charge')}%"
                write_log(status_info)

            # Обработка критического статуса
            if ups_data.get("is_critical"):
                if not critical_notification_sent:
                    for chat_id in ALLOWED_USERS:
                        await send_alert(chat_id, ups_data["message"])
                    critical_notification_sent = True
            else:
                critical_notification_sent = False

                # Обычные уведомления при изменении статуса
                if current_status != last_status and "message" in ups_data:
                    for chat_id in ALLOWED_USERS:
                        await send_alert(chat_id, ups_data["message"])

            last_status = current_status
            await asyncio.sleep(CHECK_INTERVAL)

    except asyncio.CancelledError:
        write_log("Бот остановлен по запросу")
    except Exception as e:
        write_log(f"КРИТИЧЕСКАЯ ОШИБКА: {e}")
    finally:
        write_log("===== Бот завершил работу =====")


if __name__ == "__main__":
    try:
        asyncio.run(monitor_ups())
    except KeyboardInterrupt:
        write_log("Бот остановлен в ручную")

 

 Для запуска бота потребуется добавить в init.d файл S90telegram_bot (добавлен в архив)

Спойлер
#!/bin/sh

ENABLED=yes
BOT_SCRIPT="/opt/etc/telegram_bot/bot.py"
PID_FILE="/var/run/telegram_bot.pid"
VENV_ACTIVATE="/opt/etc/telegram_bot/venv/bin/activate"

# Функция проверки работает ли процесс
is_process_running() {
    [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null
}

case "$1" in
    start)
        if is_process_running; then
            echo "Бот уже запущен (PID $(cat $PID_FILE))"
            exit 1
        fi
        
        # Очистка предыдущего PID
        rm -f "$PID_FILE"
        
        # Запуск в виртуальном окружении
        . "$VENV_ACTIVATE"
        python "$BOT_SCRIPT" &
        echo $! > "$PID_FILE"
        echo "Бот запущен (PID $(cat $PID_FILE))"
        ;;
    stop)
        if is_process_running; then
            kill -9 $(cat "$PID_FILE")
            rm -f "$PID_FILE"
            echo "Бот остановлен"
        else
            echo "Бот не запущен"
        fi
        ;;
    restart)
        $0 stop
        sleep 2
        $0 start
        ;;
    status)
        if is_process_running; then
            echo "Бот работает (PID $(cat $PID_FILE))"
        else
            echo "Бот не запущен"
        fi
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

 

Интеграция в умный дом
Для отправки сообщений в умный дом я воспользовался пакетом mosquitto-client-nossl. Был создан файл для записи данных в mqtt - ups_mqtt_report.sh и добавлен в папку крона "cron.1min" - для запуска раз в минуту. Содержание файла:

Спойлер
#!/bin/sh

UPS_NAME="myups"
MQTT_HOST="192.168.X.X"
MQTT_PORT=
#опционально, если будет логин/пароль
MQTT_USER="mqtt" 
MQTT_PASS="mqtt"

CHARGE=$(upsc "$UPS_NAME" battery.charge 2>/dev/null)

[ -n "$CHARGE" ] && mosquitto_pub -r -i ups-client -h "$MQTT_HOST" -p "$MQTT_PORT" \
    -u "$MQTT_USER" -P "$MQTT_PASS" \
    -t "ups/battery/charge" -m "$CHARGE"

 


Для получения сообщений прописываем команду

Спойлер
mosquitto_sub -h 192.168.X.X -p 000 -u mqtt -P mqtt -i ups-monitor -t "ups/battery/charge" -v
где 000 - ваш порт mqtt брокера

В Sprut Hub я добавил кастомный шаблон устройства для UPS:

Спойлер
{
  "name": "CPS Value400EI",
  "manufacturer": "CPS",
  "model": "Value400EI",
  "modelId": "(ups)/battery/charge",
  "catalogId": 0,
  "services": [
    {
      "type": "BatteryService",
      "characteristics": [
        {
          "type": "BatteryLevel",
          "link": [
            {
              "type": "Integer",
              "topicGet": "(1)/battery/charge"
            }
          ]
        }
      ]
    }
  ]
}

 

Новое устройство добавил через контроллер mqtt, добавил виртуальный датчик "батарейка" и связал её с UPS. После чего можно использовать заряд UPS для автоматизации.

telegram_bot.zip

Изменено пользователем Max871

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...

Важная информация

На этом сайте используются файлы cookie. Нажимая "Я принимаю" или продолжая просмотр сайта, вы разрешаете их использование: Политика конфиденциальности.