Home Assistant: Ragtech Nobreak Easy Pro (UPS) Monitoring

Colegas, obrigado pelo tópico, eu nem uso os aplicativos usados aqui nesse fórum, mas como estava pesquisando sobre como acessar o USB serial dos modelos Ragtech (tenho um Easy Pro 1200VA), dei uma trabalhada no script que uso abaixo. Os valores de conversão das leituras hexa foram chutados, não sei se estão 100% coerentes.

Eu integrei ele no NUT de um servidor Linux que rodo Proxmox, como um dummy driver, então o esse script executa via cron a cada minuto, gera um arquivo de dados que serve como “driver” tipo dummy para o NUT. Com isso ele dispara os comandos de shutdown/email etc. que forem necessários para desligar o servidor ou outras atividades de alta disponibilidade.

A partir do que escrevi em cima esse script, usando chatgpt voces conseguem fácil fazer a integração com o NUT.

Detalhe importante: toda vez que reinicia o Linux o /dev/ttyACM* que ele cria tem segurança alterada. Para o script funcionar tem que estar como 666, tem como fazer isso persistente com utlitarios udevadm…

Ainda estou fazendo uns testes, se alguem quiser melhorar fique à vontade. Já integrei até como Grafana via Prometheus…

#!/usr/bin/env python3
import serial
import time

SERIAL_PORT = "/dev/ttyACM0"
BAUD_RATE = 2560
TIMEOUT = 20
DATA_FILE = "/var/lib/nut/ragtech-ups.data"

REQUEST_COMMAND = bytes.fromhex("AA0400801E9E")

def parse_data(data):
    hex_str = ''.join(f'{byte:02x}' for byte in data)

    if hex_str.startswith("aa21") and len(hex_str) >= 62:
        battery_charge = round(int(hex_str[16:18], 16) * 0.393)
        input_voltage = round(int(hex_str[52:54], 16) * 1.06)
        output_voltage = round(int(hex_str[60:62], 16) * 0.555)
        temperature = int(hex_str[30:32], 16)
        load = int(hex_str[28:30], 16)

        # Enhanced status logic
        if input_voltage < 100:
            if battery_charge < 20:
                ups_status = "LB"  # Low Battery
            else:
                ups_status = "OB"  # On Battery
        else:
            ups_status = "OL"  # On Line

        # Battery replacement detection (example logic)
        if battery_charge < 5:
            ups_status = "RB"

        # Charging/Discharging logic
        if input_voltage > output_voltage:
            ups_status += " CHRG"
        elif input_voltage < output_voltage:
            ups_status += " DISCHRG"

        # Write updated data to NUT data file
        metrics = {
            "battery.charge": battery_charge,
            "input.voltage": input_voltage,
            "output.voltage": output_voltage,
            "ups.temperature": temperature,
            "ups.load": load,
            "ups.status": ups_status,
        }

        with open(DATA_FILE, 'w') as f:
            for k, v in metrics.items():
                f.write(f"{k}: {v}\n")

def main():
    try:
        with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=TIMEOUT) as ser:
            ser.write(REQUEST_COMMAND)
            time.sleep(2)
            response = ser.read(64)
            parse_data(response)
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

1 Like

Boa tarde a todos.

Mesmo problema de muita gente por aqui. Conectado mas sem “conversa”.

EASY PRO 2000 VA

Usei os jsons do breDaniel e do Gustavocmorais mas o resultado é o mesmo,

oi!
coisas para tentar…

ativa o debug OUT (pequeno botão do lado direito dele).
os comandos enviados para o nobreak do Request INFO tem que começar a aparecer ali do lado direito. alguns bytes.

clica nos botões de Request Data do Primeiro Comando e do Segundo Comando. ve se retorna algo.

no caso do meu aparelho, eles são enviados logo em seguida da conexão, antes do comando de request info. mas, no caso do meu aparelho, eles não são obrigatórios…

vai que, no seu, precisa desses comandos antes do request info…
se for o caso, precisaria adaptar o nodered pra, qdo conectar, sempre mandar os outros comandos antes.

turma, compartilho meu novo script.
1 - como meu nobreak fica longe do HA (ele fica na impressora 3d), conectei ele em um outro rasp (o mesmo que tem o octopi da 3d) que tem o ser2net fazendo a interface SERIAL-TCP. esse novo nodered foi modificado pra usar uma conexao TCP no lugar da SERIAL.
2 - refinei a parte como o buffer recebido do nobreak eh processado.

se alguem tiver tempo e se animar, poderiamos unificar com o anterior. deixando a opcao de escolher entre usar a serial direto (nobreak conectado no HA) ou remoto via TCP.

[
  {
    "id": "cee84742491cec90",
    "type": "tab",
    "label": "Ragtech Serial",
    "disabled": false,
    "info": ""
  },
  {
    "id": "e01aec5f975a2fe0",
    "type": "debug",
    "z": "cee84742491cec90",
    "name": "debug Serial IN",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "",
    "statusType": "auto",
    "x": 1000,
    "y": 620,
    "wires": []
  },
  {
    "id": "9a8ef3f6c36a13a6",
    "type": "inject",
    "z": "cee84742491cec90",
    "name": "Request Data",
    "props": [],
    "repeat": "5",
    "crontab": "",
    "once": false,
    "onceDelay": "1",
    "topic": "",
    "x": 200,
    "y": 80,
    "wires": [["64ec956feacc9b87"]]
  },
  {
    "id": "f6e354e3b9c0c84c",
    "type": "debug",
    "z": "cee84742491cec90",
    "name": "debug OUT",
    "active": false,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "",
    "statusType": "auto",
    "x": 850,
    "y": 180,
    "wires": []
  },
  {
    "id": "64ec956feacc9b87",
    "type": "function",
    "z": "cee84742491cec90",
    "name": "Request INFO",
    "func": "//PEDE STATS\nmsg.payload = Buffer.from(\"AA0400801E9E\", \"hex\")\nmsg.reset = true;\nreturn msg;\n\n\n//PEDE STATS\n//msg.payload = Buffer.from(\"AA0400F301F4AA0401360138AA0402020206AA0400801E9E\", \"hex\")\n//return msg;\n\n\n//PEDE STATS\n//msg.payload = Buffer.from(\"AA0400F301F4\", \"hex\")\n//return msg;\n\n\n",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 460,
    "y": 80,
    "wires": [["f6e354e3b9c0c84c", "915f810edd627e3b"]]
  },
  {
    "id": "8699bb5eef04e00b",
    "type": "function",
    "z": "cee84742491cec90",
    "name": "toHexString",
    "func": "const toHexString = (bytes) => {\n  return Array.from(bytes, (byte) => {\n    return ('0' + (byte & 0xff).toString(16)).slice(-2);\n  }).join(' ');\n};\n\nvar hexString = toHexString(msg.payload);\nmsg.payload = hexString;\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 790,
    "y": 620,
    "wires": [["e01aec5f975a2fe0"]]
  },
  {
    "id": "5ed339876d5fa2af",
    "type": "function",
    "z": "cee84742491cec90",
    "name": "Primeiro Comando",
    "func": "msg.payload = Buffer.from(\"A004FFE0088B\", \"hex\")\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 470,
    "y": 180,
    "wires": [["f6e354e3b9c0c84c", "915f810edd627e3b"]]
  },
  {
    "id": "f768f62acb1e8f29",
    "type": "inject",
    "z": "cee84742491cec90",
    "name": "Request Data",
    "props": [],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": "1",
    "topic": "",
    "x": 190,
    "y": 180,
    "wires": [["5ed339876d5fa2af"]]
  },
  {
    "id": "6ad30b7ec217adb8",
    "type": "function",
    "z": "cee84742491cec90",
    "name": "Segundo Comando",
    "func": "msg.payload = Buffer.from(\"FFFE008E018F\", \"hex\")\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 470,
    "y": 260,
    "wires": [["f6e354e3b9c0c84c", "915f810edd627e3b"]]
  },
  {
    "id": "ac2d21ae80912a6a",
    "type": "inject",
    "z": "cee84742491cec90",
    "name": "Request Data",
    "props": [],
    "repeat": "",
    "crontab": "",
    "once": false,
    "onceDelay": "1",
    "topic": "",
    "x": 190,
    "y": 260,
    "wires": [["6ad30b7ec217adb8"]]
  },
  {
    "id": "d6880b42400380a4",
    "type": "ha-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Temperature",
    "entityConfig": "5f030e19c85b66d7",
    "version": 0,
    "state": "payload.Temperature",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1360,
    "y": 260,
    "wires": [[]]
  },
  {
    "id": "557f343d4699e667",
    "type": "ha-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Battery",
    "entityConfig": "da00fe0c0350830d",
    "version": 0,
    "state": "payload.Battery_State",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1340,
    "y": 320,
    "wires": [[]]
  },
  {
    "id": "884f8d7eb8c05e9b",
    "type": "ha-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Input Voltage",
    "entityConfig": "1289139e401c0dbb",
    "version": 0,
    "state": "payload.Voltage_In",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1360,
    "y": 380,
    "wires": [[]]
  },
  {
    "id": "915f810edd627e3b",
    "type": "tcp request",
    "z": "cee84742491cec90",
    "name": "tcp ragtech",
    "server": "192.168.0.147",
    "port": "8888",
    "out": "count",
    "ret": "buffer",
    "splitc": "31",
    "newline": "",
    "trim": false,
    "tls": "",
    "x": 750,
    "y": 300,
    "wires": [["8699bb5eef04e00b", "4d971ed73c6fe453"]]
  },
  {
    "id": "4d971ed73c6fe453",
    "type": "function",
    "z": "cee84742491cec90",
    "name": "Processa Dados",
    "func": "// ----- Configuração -----\nconst TOTAL_BYTES = 31;  // Número esperado de bytes na resposta\nconst HEADER_BYTE = 0xAA; // Primeiro byte esperado\nconst HEADER_BYTE_2 = 0x09; // Primeiro byte esperado\n\n// ----- Buffer para armazenar os dados até completar 31 bytes -----\nvar buffer = flow.get(\"tcpBuffer\") || Buffer.alloc(0);\n\n// ----- Acumular bytes recebidos -----\nbuffer = Buffer.concat([buffer, msg.payload]);\n\n// Se ainda não temos os 31 bytes, armazenamos e aguardamos mais dados\nif (buffer.length < TOTAL_BYTES) {\n    flow.set(\"tcpBuffer\", buffer);\n    return null;\n}\n\n// Se temos mais do que 31 bytes, pegamos os 31 primeiros e descartamos o restante\nconst data = buffer.slice(0, TOTAL_BYTES);\nflow.set(\"tcpBuffer\", Buffer.alloc(0));  // Reset do buffer\n\n// ----- Validação do primeiro byte -----\nif (data[0] !== HEADER_BYTE || data[1] != HEADER_BYTE_2) {\n    node.warn(\"Resposta inválida: primeiro byte não é 0xAA\");\n    return null; // Descarta o pacote inválido\n}\n\n// ----- Extração dos dados -----\nlet iOut = parseFloat((data[13] * 0.1152).toFixed(2));  // Corrente de saída (A)\nlet vOut = parseFloat((data[30] * 0.555).toFixed(1));   // Tensão de saída (V)\nlet wOut = parseFloat((iOut * vOut).toFixed(1));        // Potência de saída (W)\n\nlet vIn = parseFloat((data[12] * 1.06).toFixed(1));     // Tensão de entrada (V)\nlet wIn = parseFloat((iOut * vIn).toFixed(1));         // Potência de entrada (W)\n\nlet batteryInUse = vIn < 20 || wIn < 5;  // Se Voltage_In < 5V ou Power_In < 5W, a bateria está sendo usada\n\nlet status = {\n    \"Power_Out_Percent\": data[14],  // Posição 0x0E\n    \"Current_Out\": iOut,  // Corrente de saída (A)\n    \"Voltage_Out\": vOut,  // Tensão de saída (V)\n    \"Voltage_In\": vIn,  // Tensão de entrada (V)\n    \"Power_Out\": wOut,  // Potência de saída (W)\n    \"Power_In\": wIn,  // Potência de entrada (W)\n    \"Temperature\": data[15],  // Posição 0x0F\n    \"Battery_State\": Math.round(data[8] * 0.392),  // Posição 0x08\n    \"Battery_Voltage\": parseFloat((data[11] * 0.0671).toFixed(2)),  // Posição 0x0B\n    \"Frequency\": parseFloat((data[24] * -0.1152 + 65).toFixed(2)),  // Posição 0x18\n    \"Battery_In_Use\": batteryInUse  // \"ON\" se estiver em uso, \"OFF\" se estiver na energia da rede\n};\n\n// ----- Enviar resposta formatada -----\nmsg.payload = status;\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 1000,
    "y": 300,
    "wires": [
      [
        "502581569e316c3d",
        "d6880b42400380a4",
        "884f8d7eb8c05e9b",
        "557f343d4699e667",
        "fc306eafa6a2bced",
        "a8debcd858ca0761",
        "292c6a834da90ce6",
        "4249875880206a83",
        "839eae68fdb6e066"
      ]
    ]
  },
  {
    "id": "502581569e316c3d",
    "type": "debug",
    "z": "cee84742491cec90",
    "name": "debug 1",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "false",
    "statusVal": "",
    "statusType": "auto",
    "x": 1180,
    "y": 120,
    "wires": []
  },
  {
    "id": "fc306eafa6a2bced",
    "type": "ha-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Power Output Percent",
    "entityConfig": "24ba83de7f3c5cad",
    "version": 0,
    "state": "payload.Power_Out_Percent",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1390,
    "y": 460,
    "wires": [[]]
  },
  {
    "id": "a8debcd858ca0761",
    "type": "ha-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Current Out",
    "entityConfig": "aa29efe6fa186870",
    "version": 0,
    "state": "payload.Current_Out",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1360,
    "y": 540,
    "wires": [[]]
  },
  {
    "id": "292c6a834da90ce6",
    "type": "ha-binary-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Battery In Use",
    "entityConfig": "53211f1120a99c42",
    "version": 0,
    "state": "payload.Battery_In_Use",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1370,
    "y": 620,
    "wires": [[]]
  },
  {
    "id": "4249875880206a83",
    "type": "ha-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Power Output",
    "entityConfig": "048810605b0c2c26",
    "version": 0,
    "state": "payload.Power_Out",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1360,
    "y": 700,
    "wires": [[]]
  },
  {
    "id": "839eae68fdb6e066",
    "type": "ha-sensor",
    "z": "cee84742491cec90",
    "name": "Ragtech Battery Voltage",
    "entityConfig": "026beef6bd22c407",
    "version": 0,
    "state": "payload.Battery_Voltage",
    "stateType": "msg",
    "attributes": [],
    "inputOverride": "allow",
    "outputProperties": [],
    "x": 1370,
    "y": 780,
    "wires": [[]]
  },
  {
    "id": "5f030e19c85b66d7",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Temperature",
    "version": "6",
    "entityType": "sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Temperature" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "temperature" },
      { "property": "unit_of_measurement", "value": "°C" },
      { "property": "state_class", "value": "" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "da00fe0c0350830d",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Battery",
    "version": "6",
    "entityType": "sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Battery" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "battery" },
      { "property": "unit_of_measurement", "value": "%" },
      { "property": "state_class", "value": "" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "1289139e401c0dbb",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Input Voltage",
    "version": "6",
    "entityType": "sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Input Voltage" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "voltage" },
      { "property": "unit_of_measurement", "value": "V" },
      { "property": "state_class", "value": "" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "24ba83de7f3c5cad",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Power Output Percent",
    "version": 6,
    "entityType": "sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Power Output Percent" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "power_factor" },
      { "property": "unit_of_measurement", "value": "%" },
      { "property": "state_class", "value": "measurement" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "aa29efe6fa186870",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Current Out",
    "version": 6,
    "entityType": "sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Current Out" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "current" },
      { "property": "unit_of_measurement", "value": "A" },
      { "property": "state_class", "value": "measurement" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "53211f1120a99c42",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Battery In Use",
    "version": 6,
    "entityType": "binary_sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Battery In Use" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "048810605b0c2c26",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Power Output",
    "version": 6,
    "entityType": "sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Power Output" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "power" },
      { "property": "unit_of_measurement", "value": "W" },
      { "property": "state_class", "value": "" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "026beef6bd22c407",
    "type": "ha-entity-config",
    "server": "9074df74.a057",
    "deviceConfig": "e2047f52686b2525",
    "name": "Ragtech Battery Voltage",
    "version": 6,
    "entityType": "sensor",
    "haConfig": [
      { "property": "name", "value": "Ragtech Battery Voltage" },
      { "property": "icon", "value": "" },
      { "property": "entity_picture", "value": "" },
      { "property": "entity_category", "value": "" },
      { "property": "device_class", "value": "voltage" },
      { "property": "unit_of_measurement", "value": "V" },
      { "property": "state_class", "value": "" }
    ],
    "resend": false,
    "debugEnabled": false
  },
  {
    "id": "9074df74.a057",
    "type": "server",
    "name": "Home Assistant",
    "addon": true
  },
  {
    "id": "e2047f52686b2525",
    "type": "ha-device-config",
    "name": "Nobreak Ragtech Escritorio",
    "hwVersion": "4162",
    "manufacturer": "Ragtech",
    "model": "NEP 1200VA",
    "swVersion": ""
  }
]

Pessoal! Bom dia a todos!

Percebi aqui a dificuldade de integrar os ups da rsgtech e fiquei feliz porque conseguiram.

Estou querendo comprar um nobreak basico só para meu mini pc que roda o ha + roteador.

Dos modelos que achei nesta proposta, só achei o RAGTECH – Nobreak New Easy Pro NEP 600 – 600VA/300W (tem usb).

Alguém sabe dizer se tem.compatibiludade? Ou se algum destes codigos que foram tentados por vocês funciona neste aparelho?

Estou em duvida neste ou em um intelbras(que nao tem usb, mas é mais versatil e design mais bonito)

@gustavocmorais você sabe me informar?voce conseguiu?

Bom dia, Gustavo.

Consegui uma imgem de container com o supervise instalado e funcionou de dentro de uma VM.

Pra mim já está de bom tamanho.

consegues monitorar a comunicação serial? capturar os bytes que vem e vão…
desde a primeira vez que o programar rodar… e alguns minutos depois…

Rapaz … o OS é um FreeBSD compartilhando a USB com a VM. A VM (VirtualBox) é um UBUNTU compartilhando a USB com um container rodando o supervise. Eu só consegui fazer isso funcionar ontem.

Ainda tenho um caminho a percorrer para saber o que o container está rodando.

Até chegar na captura da serial vai levar um tempinho.

Bom dia a todos.

Construi uma solução final que faz consultas a API que roda no container do Supervise. Com base nos valores retornados, um script que roda no cron decide se faz o shutdown do servidor. Ao mesmo tempo, manda uma mensagem post pra um bot do telegram, que me avisa o que está contecendo.

O http-post eu mesmo escrevi.

Obrigado a todos pela ajuda.

Gostaria de Avisa-lo que seu script está perfeito, fiz uma implementação e funcionou corretamente. Muito OBRIGADO!

EDIT: ( ultimo e finalizado!).
Fiz algumas alterações para já calcular o fator potencia, ( de acordo com dump serial), e tambem no caso tenho modulos de baterias e comparei os valores com o app da Ragtech e também já coloquei no script a partir das infos de fator potencia e etc…
Ficou assim:

#!/usr/bin/env python3
import serial
import time

SERIAL_PORT = "/dev/ttyACM0"
BAUD_RATE = 2560
TIMEOUT = 20
DATA_FILE = "/var/lib/nut/ragtech-ups.data"

REQUEST_COMMAND = bytes.fromhex("AA0400801E9E")
#VALORES NOMINAIS
def parse_data(data):
    hex_str = ''.join(f'{byte:02x}' for byte in data)

    if hex_str.startswith("aa21") and len(hex_str) >= 62:
        battery_charge = round(int(hex_str[16:18], 16) * 0.393)
        input_voltage = round(int(hex_str[52:54], 16) * 1.06)
        output_voltage = round(int(hex_str[60:62], 16) * 0.555)
        temperature = int(hex_str[30:32], 16)
        load = int(hex_str[28:30], 16)
        battery_voltage = round(int(hex_str[22:24], 16) * 0.1342, 2)
        frequency = round(int(hex_str[48:50], 16) * -0.1152 + 65, 2)
        current_out = round(int(hex_str[26:28], 16) * 0.120, 2)

        apparent_power = round(output_voltage * current_out, 1)
        #Fator Potencia, de 0.7, neste caso 1.3 será o calculo
        power_factor = 1.3
        real_power = round(apparent_power * power_factor, 1)
        #Eficiencia = Fator da Eficiencia, os calculos com o multimetrio e também baseado no output do app da ragtech oficial
        efficiency = 0.60
        current_in = 0.0
        if input_voltage > 0:
            current_in = round((output_voltage * current_out) / (input_voltage * efficiency), 2)
        # Enhanced status logic
        if input_voltage < 100:
            if battery_charge < 30:
                ups_status = "LB"  # Low Battery
            else:
                ups_status = "OB"  # On Battery
        else:
            ups_status = "OL"  # On Line

        # Battery replacement detection (example logic)
        if battery_charge < 5:
            ups_status = "RB"

        # Charging/Discharging logic
        if input_voltage > output_voltage:
            ups_status += " CHRG"
        elif input_voltage != 0 and abs(input_voltage - output_voltage) / input_voltage > 0.10:
            ups_status += " DISCHRG"
        # Write updated data to NUT data file
        metrics = {
            "battery.charge": battery_charge,
            "battery.voltage": battery_voltage,
            "input.voltage": input_voltage,
            "input.current": current_in,
            "input.frequency": frequency,
            "output.voltage": output_voltage,
            "output.current": current_out,
            "output.power": apparent_power,
            "output.apower": real_power,
            "ups.temperature": temperature,
            "ups.load": load,
            "ups.status": ups_status,
        }

        with open(DATA_FILE, 'w') as f:
            for k, v in metrics.items():
                f.write(f"{k}: {v}\n")

def main():
    try:
        with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=TIMEOUT) as ser:
            ser.write(REQUEST_COMMAND)
            time.sleep(2)
            response = ser.read(64)
            parse_data(response)
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

Eu não possuio um bom multimetro, mas as infos estão mais proximas informadas com o hardware e também de acordo com o O aplicativo da RagTech.

OBS: a eficiencia deste UPS são de 0.60 ( achei extremamente baixo).

Também Configurei um dashboard com o telegraf e influxdb, usei este link abaixo:

E o dashboard, do grafana vou adicionar o link do meu github cm as infos completas e atualizarei o post aqui.

ChatGPT me ajudou a auxiliar em algumas configurações e os valores, pode conter erro e tbm ajustes sobre a bateria ( no caso eu tenho 2 auxiliares)

Resuscitando o tópico para informar que comprei um Ragtech quad e estou trabalhando na implantação de um código Python que funcione com ele, assim dá para usar as integrações nativas dele com o HA.

Inicialmente vou suportar apenas o meu device, mas gostaria de testadores para isso… de qualquer device que seja. Eu não consigo rodar o docker com o supervise porque ele roda apenas em amd64, e o meu SBC é processador ARM. Rodei então uma instalação do supervise em um Proxmox e consegui capturar o protocolo, mas também observei que todo o protocolo está descrito no arquivo devices.xml no diretório do supervise. Ali descreve o padding de cada informação, assim como as conversões - arquivo aqui: . O objetivo final é ter as mesmas informações disponíveis no supervise, sem precisar dele instalado, e no final portar a solução para o nut.

Quem quiser acompanhar o progresso ou ajudar, pode seguir por aqui:
https://github.com/lucianor/ragtech/

Fiquei uns 3 dias tentando fazer isso funcionar no meu “NEP 1200s USB TI” modelo 4162 e não conseguia. Agora tive um tempo de dar uma olhada mais cuidadosa no codigo e, olhando o valor do hex_str, percebi que o a resposta do meu UPS comeca com aa01 ao inves de aa21. Troquei a string do if e boom, tudo comecou a funcionar.

Agora, não posso garantir que os dados estão corretos, mas tudo parece estar funcionando normal. Testei tirando da parede e a temperatura comecou a aumentar, o load mudou quando eu desliguei e liguei coisas, a percentage da bateria comecou a mudar como ela normalmente mudava quando eu supervisionava ele com o aplicativo da ragtech. Aparentemente, tudo correto!

Vou dar uma olhada no devices.xml mais tarde e ver as variaveis pra ver se tao iguais ao que voce fez (ja que tem um easy 1200 TI e M2 na Familia 10 e uns easyway 1200 ti na familia 14 e eu nao sei quais voce utilizou e, sincermaente, nao sei qual o meu se encaixa :D)

Agora, alguem podia portar esse codigo e fazer um NUT driver pra essas ragtech né? :stuck_out_tongue:

MUITO obrigado pelo script e por toda a pesquisa e o effort pra entender o protocolo da Ragtech. Sem isso, nada poderia ter sido feito.

@lucianor Conseguiu avançar aqui? Também tenho quad e posso te auxiliar.

O software oficial da Ragtech funciona bem, eu integrei no HA via MQTT com o codigo ragtech.py · GitHub

O problema é não ter um binario para ARM de forma a rodar em um RPi.

Eu acabei comprando uma CPU Dell Wyse 3040 (Amd64) para ser a ponte entre um UPS e o HA.

De qualquer forma, pra quem quiser ir mais fundo, o binario pode ser aberto no Ghidra.