I did in phython and it worked good, maybe can be useful for someone:
import tkinter as tk
from tkinter import ttk
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import threading
import time
import serial.tools.list_ports
client = None
conectado = False
thread_running = False
# Contadores
total_requisicoes = 0
total_respostas = 0
total_erros = 0
total_reenviados = 0
root = tk.Tk()
root.title("Monitoramento e Controle da Bomba")
labels = {}
campos = {}
registros = [
("Status (Hz)", 0x3000, 1, ""),
("Frequência Atual (Hz)", 0x7000, 0.01, "Hz"),
("Frequência Configurada (Hz)", 0x7001, 0.01, "Hz"),
("Tensão do Barramento DC (V)", 0x7002, 0.1, "V"),
("Tensão de Saída Motor (V)", 0x7003, 1, "V"),
("Corrente de Saída (A)", 0x7004, 0.1, "A"),
("Potência de Saída (kW)", 0x7005, 0.01, "kW"),
("Carga (%)", 0x7006, 0.1, "%"),
("X state", 0x7007, 1, ""),
("DO state", 0x7008, 1, ""),
("Tensão AI1 (V)", 0x7009, 0.01, "V"),
("Entrada AI2 (V/mA)", 0x700A, 0.01, "V/mA"),
("Tensão AI2 (V)", 0x700B, 0.01, "V"),
("Count value", 0x700C, 1, ""),
("Length value", 0x700D, 1, ""),
("Load speed", 0x700E, 1, ""),
("PID setting", 0x700F, 1, ""),
("PID feedback", 0x7010, 1, ""),
("PLC stage", 0x7011, 1, ""),
("Input pulse frequency", 0x7012, 0.01, "kHz"),
("Feedback speed (Hz)", 0x7013, 0.01, "Hz"),
("Tempo de Operação (min)", 0x7014, 0.1, "min"),
("Tensão A1 antes da correção (V)", 0x7015, 0.001, "V"),
("Saída AO1 (V/mA)", 0x7016, 0.01, "V/mA"),
("Tensão AI3 (V)", 0x7017, 0.001, "V"),
("Velocidade linear (m/min)", 0x7018, 1, "m/min"),
("Tempo Inversor Ligado (min)", 0x7019, 1, "min"),
("Tempo Motor Ligado (min)", 0x701A, 0.1, "min"),
("Frequência AI3 (Hz)", 0x701B, 1, "Hz"),
("Communication setting value (%)", 0x701C, 0.01, "%"),
("Encoder feedback speed (%)", 0x701D, 0.01, "%"),
("Main frequency X (%)", 0x701E, 0.01, "%"),
("Auxiliary frequency Y (%)", 0x701F, 0.01, "%"),
("Temperatura Motor (°C)", 0x7022, 1, "°C"),
("Target Torque (%)", 0x7023, 0.1, "%"),
("Target voltage upon V/F separation (V)", 0x7027, 1, "V"),
("Output voltage upon V/F separation (V)", 0x7028, 1, "V"),
("X function state visual display 1", 0x702B, 1, ""),
("X function state visual display 2", 0x702C, 1, ""),
("Fault information", 0x702D, 1, ""),
("Phaze Z counting", 0x703A, 1, ""),
("Current set frequency (%)", 0x703B, 0.01, "%"),
("Current running frequency (%)", 0x703C, 0.01, "%")
]
def atualizar_qualidade():
lbl_total.config(text=f"Requisições: {total_requisicoes}")
lbl_sucesso.config(text=f"Recebidas: {total_respostas}")
lbl_erros.config(text=f"Erros: {total_erros}")
lbl_reenviados.config(text=f"Reenviadas: {total_reenviados}")
def interpretar_status(valor):
return {
0x0001: "RUN FORWARD",
0x0002: "RUN REVERSE",
0x0003: "STOP"
}.get(valor, f"DESCONHECIDO (0x{valor:04X})")
def tentativa_leitura_registro(endereco, tentativas=3, delay=0.3):
global total_requisicoes, total_respostas, total_erros, total_reenviados
for i in range(tentativas):
total_requisicoes += 1
resposta = client.read_holding_registers(endereco, 1, unit=1)
if hasattr(resposta, 'registers'):
total_respostas += 1
if i > 0:
total_reenviados += i
return resposta.registers[0]
total_erros += 1
time.sleep(delay)
return None
def tentativa_leitura_bloco(inicio, tamanho, tentativas=3, delay=0.3):
global total_requisicoes, total_respostas, total_erros, total_reenviados
for i in range(tentativas):
total_requisicoes += 1
resposta = client.read_holding_registers(inicio, tamanho, unit=1)
if hasattr(resposta, 'registers'):
total_respostas += 1
if i > 0:
total_reenviados += i
return resposta.registers
total_erros += 1
time.sleep(delay)
return None
def atualizar_dados():
global conectado, thread_running
bloco_inicio = 0x7000
bloco_fim = 0x703C
bloco_tamanho = bloco_fim - bloco_inicio + 1
while conectado:
try:
bruto = tentativa_leitura_registro(0x3000)
if bruto is not None:
campos["Status (Hz)"].config(text=interpretar_status(bruto))
else:
campos["Status (Hz)"].config(text="Erro")
blocos = tentativa_leitura_bloco(bloco_inicio, bloco_tamanho)
for nome, endereco, fator, unidade in registros:
if endereco == 0x3000:
continue
if blocos:
try:
bruto = blocos[endereco - bloco_inicio]
valor = round(bruto * fator, 2)
texto = f"{valor} {unidade}".strip()
except:
texto = "Erro"
else:
texto = "Erro"
campos[nome].config(text=texto)
time.sleep(0.02)
atualizar_qualidade()
time.sleep(1.0)
except Exception as e:
print(f"Erro geral durante leitura: {e}")
time.sleep(2)
thread_running = False
def conectar():
global client, conectado, thread_running
porta = combo_portas.get()
client = ModbusClient(method='rtu', port=porta, baudrate=9600, timeout=3, stopbits=1, bytesize=8, parity='N')
if client.connect():
conectado = True
btn_conectar.config(state="disabled")
btn_desconectar.config(state="normal")
if not thread_running:
threading.Thread(target=atualizar_dados, daemon=True).start()
thread_running = True
def desconectar():
global conectado, client
conectado = False
if client:
client.close()
btn_conectar.config(state="normal")
btn_desconectar.config(state="disabled")
def listar_portas():
return [p.device for p in serial.tools.list_ports.comports()]
# UI
coluna = 0
for i, (nome, *_rest) in enumerate(registros):
linha = i % 20
if i > 0 and linha == 0:
coluna += 2
tk.Label(root, text=nome).grid(row=linha, column=coluna, sticky="w")
campo = tk.Label(root, text="---", width=20, anchor="w")
campo.grid(row=linha, column=coluna + 1, sticky="w")
campos[nome] = campo
# CONTROLE
frame_controle = tk.Frame(root)
frame_controle.grid(row=len(registros)+1, column=0, columnspan=6, pady=10)
btn_on = tk.Button(frame_controle, text="Ligar Bomba", bg="green", fg="white", command=lambda: client.write_register(0x2000, 1, unit=1))
btn_on.pack(side="left", padx=5)
btn_off = tk.Button(frame_controle, text="Desligar Bomba", command=lambda: client.write_register(0x2000, 6, unit=1), bg="red", fg="white")
btn_off.pack(side="left", padx=5)
btn_off = tk.Button(frame_controle, text="Fault Reset", command=lambda: client.write_register(0x2000, 7, unit=1), bg="blue", fg="white")
btn_off.pack(side="left", padx=5)
slide_freq = tk.Scale(frame_controle, from_=10, to=60, label="Frequência (Hz)", orient="horizontal")
slide_freq.pack(side="left", padx=5)
btn_set_freq = tk.Button(frame_controle, text="Enviar Frequência", command=lambda: client.write_register(0x1000, int(slide_freq.get() * (10000 / 60)), unit=1))
btn_set_freq.pack(side="left", padx=5)
combo_portas = ttk.Combobox(frame_controle, values=listar_portas(), width=10)
combo_portas.pack(side="left", padx=5)
combo_portas.set("COM10")
btn_conectar = tk.Button(frame_controle, text="Conectar", command=conectar)
btn_conectar.pack(side="left", padx=5)
btn_desconectar = tk.Button(frame_controle, text="Desconectar", command=desconectar, state="disabled")
btn_desconectar.pack(side="left", padx=5)
# QUALIDADE
frame_qualidade = tk.Frame(root)
frame_qualidade.grid(row=len(registros)+2, column=0, columnspan=6, pady=5)
lbl_total = tk.Label(frame_qualidade, text="Requisições: 0")
lbl_total.pack(side="left", padx=10)
lbl_sucesso = tk.Label(frame_qualidade, text="Recebidas: 0")
lbl_sucesso.pack(side="left", padx=10)
lbl_erros = tk.Label(frame_qualidade, text="Erros: 0")
lbl_erros.pack(side="left", padx=10)
lbl_reenviados = tk.Label(frame_qualidade, text="Reenviadas: 0")
lbl_reenviados.pack(side="left", padx=10)
def on_closing():
desconectar()
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()