tldr: ИБП использует Modbus (а не Megatec/Voltronic), параметры BAUDRATE = 9600, DEVICE_ID = 10, START_ADDR = 30000.
У меня была задача - купить ИБП для домашней системы хранения / homelab сервера.
Идея была купить что-то не слишком дорогое, с возможностью подключения внешних АКБ и мониторинга через стандартный протокол NUT (Network UPS Tools, стандартный протокол по которому можно мониторить UPS в linux, NAS типа Synology и т.д.) .
Выбор пал на Энергия Smart.2 1000 Вт. К слову, perplexity среди бюджетных вариантов не нашел его, помогли собственные знания.
В DNS он стоил 13 тыс, на Озон была куплена LiFePo4 100 Ah батарея еще за 20к, и комплект был готов.
Сразу скажу, что у него есть возможность выбора АКБ в параметре 14 - Pb или Li, так вот выбирать Li не надо. В этом режиме он держит постоянно absorption (14.5 В), не уходя во float.
Легко подключить к компу ИБП не удалось.
По версии perpelexity, в этом ИБП используется протокол Megatec / Voltronic и вопрос в подборе скоростей порта и других параметров. Когда перебор всех вариантов не привел к результату, стал разбираться с платой, которая конверирует UART в USB.
Разобрал ИБП, вынул плату (лишился гарантии попутно, оборвав пломбу), прозвонил входящий шлейф, выяснил где оптопары, побился с подачей напряжения через флаги DTR/RTS, ничего не помогло.
Был уже готов покупать отдельный UART - USB адаптер.
Решил дать еще один шанс ПО, которое указано на сайте производителя. В поделку под названием "SolarPowerMonitor2.2.81" веры было мало (какой нафиг Solar?), но не под виртуальной, а bare metal Windows, ИБП завелся.

Дальше все было просто.
Я: Так, я смог подключился к ИБП через ПО SolarPowerMonitor 2.2.81.
ИБП определяется как Ep2000Pro. В настройках этого ПО указано что EPSeries подключается с devices id = 10, Baudrate = 9600, machinename Address = 30000. Давай поймем что это такое и по какому протоколу с ним нужно работать.
Perp: Это просто колоссальный прорыв! Вы только что взломали главную загадку этого ИБП. Забудьте всё, что мы обсуждали про протоколы Megatec/Voltronic, скорости 2400 бод и команды Q1. Ваш ИБП «Энергия Smart.2» — это OEM-клон инвертора MUST EP2000 PRO. А устройства компании MUST (как и многие современные солнечные инверторы) общаются не простым текстом, а по промышленному протоколу Modbus RTU.
Прям называть Энергию "клоном" я бы не стал (как минимум другие токи зарядки), но вдохновлялись явно Must.
Дальше было делом техники понять, что мониторить по modbus встроенные в NUT драйвера не могут, и нужно использовать dummy-ups драйвер, который будет читать параметры UPS из файла.
Пример конфига:
sudo tee /etc/nut/ups.conf <<EOF [energy_ups] driver = dummy-ups port = /var/lib/nut/energy_ups.dev desc = "Energy Smart.2 1000" EOF
А в файл /var/lib/nut/energy_ups.dev будет писать отдельный скрипт, запущенный (например) в виде службы.
Пользователя, который будет запускать скрипт нужно добавить в группу dialout (ubuntu) или uucp (arch).
Он же должен мочь (chown) писать в /var/lib/nut.
Текст скрипта для мониторинга на python ниже. Если запускать его как "./script.py print" - выведет данные в консоль.
Скрытый текст
#!/usr/bin/env python3 from pymodbus.client import ModbusSerialClient from pymodbus.framer import FramerType import os import time import sys # Проверяем, запущен ли скрипт с аргументом "print" ENABLE_PRINT = len(sys.argv) > 1 and sys.argv[1] == "print" #Set battery data BATTERY_CAPACITY_AH = 100 BATTERY_VOLTAGE_NOMINAL = 12 BATTERY_TYPE = "LiFePO4" #file to write data for NUT dummy_file = "/var/lib/nut/energy_ups.dev" tmp_file = "/var/lib/nut/energy_ups.dev.tmp" # how often to take data from UPS POLL_INTERVAL = 2 PORT = '/dev/ttyUSB0' BAUDRATE = 9600 DEVICE_ID = 10 START_ADDR = 30000 # max readable = 25 COUNT = 20 SOC_100_VOLTAGE = 13.6 SOC_0_VOLTAGE = 12 # Своя функция печати, которая выводит текст только если передан аргумент 'print' def debug_print(msg): if ENABLE_PRINT: print(msg) # write UPS data to file (NUT will read this file) def write_to_file(data_string): # Записываем во временный файл with open(tmp_file, "w") as f: f.write(data_string) # Атомарно заменяем старый файл новым os.replace(tmp_file, dummy_file) def read_ups_modbus(): # Инициализация Modbus RTU клиента (самый новый синтаксис) client = ModbusSerialClient( port=PORT, baudrate=BAUDRATE, framer=FramerType.RTU, # Указываем тип фреймера через enum timeout=1, parity='N', stopbits=1, bytesize=8 ) print(f"Запуск службы мониторинга ИБП...") # 2. БЕСКОНЕЧНЫЙ ЦИКЛ ОПРОСА while True: try: # Проверяем, открыт ли порт. Если нет - пытаемся открыть. if not client.connected: print(f"Подключение к {PORT}...") if client.connect(): print(f"Подключено к {PORT}...") else: print("Не удалось подключиться. Повтор через 5 секунд...") time.sleep(5) continue debug_print("Читаем регистры (Holding)...") # Читаем 20 регистров начиная с 30000 для устройства с ID=10 response = client.read_holding_registers(address=START_ADDR, count=COUNT, device_id=DEVICE_ID) if response.isError(): print("Ошибка Holding Registers. Пробуем Input Registers...") response = client.read_input_registers(address=START_ADDR, count=COUNT, device_id=DEVICE_ID) if not response.isError(): debug_print("\n=== ДАННЫЕ ПОЛУЧЕНЫ ===") for i, val in enumerate(response.registers): real_address = START_ADDR + i debug_print(f"Регистр {real_address}: {val}") status = response.registers[2] rated_w = response.registers[4] batt_v = response.registers[14] / 10.0 batt_soc = response.registers[17] batt_current = response.registers[15] / 10.0 in_v = response.registers[5] / 10.0 in_freq = response.registers[6] / 10.0 out_v = response.registers[7] / 10.0 out_freq = response.registers[8] / 10.0 temp = response.registers[18] load_p = response.registers[12] load_power = response.registers[10] # calc real SOC real_soc = (batt_v - SOC_0_VOLTAGE) / (SOC_100_VOLTAGE - SOC_0_VOLTAGE) * 100 # SOC min / max levels for NUT if real_soc > 100: real_soc = 100 if real_soc < 0: real_soc = 0 # --- ФОРМИРУЕМ СТАТУС ДЛЯ NUT --- status_flags = [] if status == 4: status_flags.append("OL") # От сети elif status == 3: status_flags.append("OB") # От батареи else: status_flags.append("WAIT") # Дополнительные флаги if batt_current > 0 and "OL" in status_flags: status_flags.append("CHRG") # Заряжается # Если батарея садится - даем сигнал Low Battery (чтобы сервер завершил работу) if real_soc <= 10 and "OB" in status_flags: status_flags.append("LB") ups_status_string = " ".join(status_flags) # Вывод на экран data_to_show=f"""status: {status} (3 = on battery, 4 = online) batt_v: {batt_v} batt_current: {batt_current} batt_soc: {batt_soc}% / real {real_soc:.0f}% load_power: {load_power} load_percent: {load_p}% in_v: {in_v} / {in_freq} out_v: {out_v} / {out_freq} temp: {temp} rated_w: {rated_w} """ debug_print(f"{data_to_show}") # --- ЗАПИСЬ В ФАЙЛ --- data_to_write = f"""device.mfr: Energy Smart.2 device.model: {rated_w}W ups.status: {ups_status_string} ups.power.nominal: {rated_w} ups.realpower.nominal: {rated_w} ups.load: {load_p} ups.realpower: {load_power} ups.temperature: {temp} battery.capacity: {BATTERY_CAPACITY_AH} battery.type: {BATTERY_TYPE} battery.voltage.nominal: {BATTERY_VOLTAGE_NOMINAL} battery.charge: {real_soc:.0f} battery.voltage: {batt_v} battery.current: {batt_current} input.voltage: {in_v} input.frequency: {in_freq} output.voltage: {out_v} output.frequency: {out_freq} """ write_to_file(data_to_write) debug_print(f"Данные обновлены: {ups_status_string} | {batt_v}V | {in_v}V") else: print(f"ИБП ответил ошибкой Modbus: {response}") # Если ИБП перестал отвечать, закроем соединение, # чтобы на следующем цикле оно переоткрылось заново client.close() except Exception as e: print(f"Системная ошибка (отвалился USB?): {e}") client.close() time.sleep(5) # Пауза перед попыткой переподключения # Ждем 1 секунду до следующего опроса time.sleep(POLL_INTERVAL) if __name__ == '__main__': read_ups_modbus()
P.S. Так же по NUT этот же UPS можно пробросить в любые другие компы / NAS. Или в Home Assistant, чтобы было например так:
Скрытый текст

