Apptk 001
Apptk 001
import serial
import serial.tools.list_ports
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import time
import json
import re
import os
import tkinter.font as tkfont
CONFIG_FILE = 'config.json'
class SerialGUI(tk.Tk):
def __init__(self):
super().__init__()
# --- Tăng kích thước font mặc định thêm 15% ---
for name in ("TkDefaultFont", "TkTextFont", "TkFixedFont", "TkMenuFont",
"TkHeadingFont"):
try:
f = tkfont.nametofont(name)
f.configure(size=int(f.cget("size") * 1.2))
except tk.TclError:
pass
self.title("E-well Gateway")
self.state('zoomed')
self.protocol("WM_DELETE_WINDOW", self.on_close)
# Inputs
inputs = [
("WiFi SSID:", self.wifi_ssid),
("WiFi Pass:", self.wifi_pass),
("Username:", self.username),
("Password:", self.user_pass),
("MQTT Server:", self.mqtt_broker),
("MQTT Port:", self.mqtt_port)
]
for i, (lbl, var) in enumerate(inputs):
ttk.Label(frame_cfg, text=lbl).grid(row=i, column=0, padx=6, pady=6,
sticky='e')
width = 32 if isinstance(var, tk.StringVar) else 12
ttk.Entry(frame_cfg, textvariable=var, width=width).grid(row=i,
column=1, padx=6, pady=6, sticky='w')
row = len(inputs)
# Gateway SN
ttk.Label(frame_cfg, text="SN Gateway:").grid(row=row, column=0, padx=6,
pady=6, sticky='e')
ttk.Entry(frame_cfg, textvariable=self.gateway_sn, width=32).grid(row=row,
column=1, padx=6, pady=6, sticky='w')
# Device count
ttk.Label(frame_cfg, text="Số lượng thiết bị:").grid(row=row+1, column=0,
padx=6, pady=6, sticky='e')
spin = tk.Spinbox(frame_cfg, from_=1, to=30, textvariable=self.dev_count,
width=6, command=self.update_sn_entries)
spin.grid(row=row+1, column=1, padx=6, pady=6, sticky='w')
# SN entries
self.sn_frame = ttk.Frame(frame_cfg)
self.sn_frame.grid(row=row+2, column=0, columnspan=2, pady=8)
self.update_sn_entries()
# Right pane: serial & log
right = ttk.Frame(pw)
pw.add(right, weight=2)
# Connection frame
conn = ttk.Labelframe(right, text="Kết nối Gateway")
conn.pack(fill='x', padx=12, pady=(12,6))
ttk.Label(conn, text="Cổng COM:").grid(row=0, column=0, padx=6)
self.cmb_port = ttk.Combobox(conn, values=self.get_ports(), width=12)
self.cmb_port.grid(row=0, column=1, padx=6)
ttk.Button(conn, text="Làm mới", command=self.refresh_ports).grid(row=0,
column=2, padx=6)
ttk.Label(conn, text="Baud rate:").grid(row=0, column=3, padx=6)
self.cmb_baud = ttk.Combobox(conn,
values=[300,1200,2400,4800,9600,19200,38400,57600,115200], width=10)
self.cmb_baud.set(9600)
self.cmb_baud.grid(row=0, column=4, padx=6)
self.btn_connect = ttk.Button(conn, text="Kết nối", command=self.connect)
self.btn_connect.grid(row=0, column=5, padx=6)
self.btn_disconnect = ttk.Button(conn, text="Ngắt",
command=self.disconnect, state='disabled')
self.btn_disconnect.grid(row=0, column=6, padx=6)
# Log frame
logf = ttk.Labelframe(right, text="Dữ liệu nhận về")
logf.pack(fill='both', expand=True, padx=12, pady=6)
self.txt_log = scrolledtext.ScrolledText(logf, wrap='none',
font=('Consolas',11))
self.txt_log.pack(fill='both', expand=True, padx=6, pady=6)
# Action frame
act = ttk.Frame(right)
act.pack(fill='x', padx=12, pady=6)
self.btn_clear_data = tk.Button(act, text="Xóa dữ liệu", bg='red',
fg='white', command=self.clear_data, state='disabled')
self.btn_clear_data.pack(side='left', padx=(0,24))
self.btn_save = tk.Button(act, text="Lưu", bg='green', fg='white',
command=self.save_config, state='disabled')
self.btn_save.pack(side='left', padx=12)
self.btn_clear_log = ttk.Button(act, text="Xóa tin nhắn",
command=self.clear_log, state='disabled')
self.btn_clear_log.pack(side='right', padx=6)
def update_sn_entries(self):
for w in self.sn_frame.winfo_children():
w.destroy()
self.sn_vars.clear()
for i in range(self.dev_count.get()):
var = tk.StringVar(value="")
self.sn_vars.append(var)
ttk.Label(self.sn_frame, text=f"SN thiết bị {i+1}:").grid(row=i,
column=0, padx=6, pady=4, sticky='e')
ttk.Entry(self.sn_frame, textvariable=var, width=32).grid(row=i,
column=1, padx=6, pady=4, sticky='w')
def get_ports(self):
return [p.device for p in serial.tools.list_ports.comports()]
def refresh_ports(self):
self.cmb_port['values'] = self.get_ports()
def connect(self):
port = self.cmb_port.get()
baud = int(self.cmb_baud.get() or 0)
if not port or not baud:
messagebox.showwarning("Thiếu thông tin", "Chọn cổng và baudrate!")
return
try:
self.ser = serial.Serial(port, baud, timeout=0.1)
self.ser.dtr = False
time.sleep(0.05)
self.ser.dtr = True
self.ser.reset_input_buffer()
except Exception as e:
messagebox.showerror("Kết nối lỗi", str(e))
return
for b in [self.btn_clear_data, self.btn_clear_log, self.btn_disconnect]:
b.config(state='normal')
self.btn_connect.config(state='disabled')
self._stop_read.clear()
threading.Thread(target=self._read_thread, daemon=True).start()
self.log(f"✔ Đã kết nối {port} @ {baud}")
def disconnect(self):
if self.ser and self.ser.is_open:
self._stop_read.set()
self.ser.close()
for b in [self.btn_clear_data, self.btn_clear_log, self.btn_disconnect]:
b.config(state='disabled')
self.btn_connect.config(state='normal')
self.log("✖ Đã ngắt kết nối")
def clear_data(self):
if self.ser:
msg = "Đang xóa dữ liệu trước đó vui lòng không thao tác thêm ..."
self.ser.write((msg + "\n").encode('utf-8'))
self.log(f"{msg}")
def save_config(self):
if self.ser and self.ser.is_open:
self.ser.write(("y\n").encode('utf-8'))
self.log("y")
self.btn_save.config(state='disabled')
cfg = {
'ssid': self.wifi_ssid.get(),
'wifi_pass': self.wifi_pass.get(),
'username': self.username.get(),
'user_pass': self.user_pass.get(),
'mqtt_broker': self.mqtt_broker.get(),
'mqtt_port': self.mqtt_port.get(),
'gateway_sn': self.gateway_sn.get(),
'devices': self.dev_count.get(),
'serials': [v.get() for v in self.sn_vars]
}
try:
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(cfg, f, ensure_ascii=False, indent=2)
except Exception as e:
messagebox.showerror("Lưu lỗi", str(e))
def clear_log(self):
self.txt_log.delete('1.0', tk.END)
def send_config(self):
cfg = {
'ssid': self.wifi_ssid.get(),
'wifi_pass': self.wifi_pass.get(),
'username': self.username.get(),
'user_pass': self.user_pass.get(),
'mqtt_broker': self.mqtt_broker.get(),
'mqtt_port': self.mqtt_port.get(),
'gateway_sn': self.gateway_sn.get(),
'devices': self.dev_count.get(),
'serials': [v.get() for v in self.sn_vars]
}
msg = json.dumps(cfg)
self.ser.write((msg + "\n").encode('utf-8'))
self.log(f"{msg}")
def _read_thread(self):
while not self._stop_read.is_set():
try:
raw = self.ser.readline()
if not raw:
continue
line = raw.decode('utf-8', errors='ignore').strip()
self.log(line)
# strip parentheses
key = re.sub(r"\(.*\)", "", line.lower()).rstrip(':').strip()
if key == 'xác nhận lưu cấu hình':
self.btn_save.config(state='normal')
continue
if key == 'seri gateway':
resp = self.gateway_sn.get()
elif re.match(r'nhập số công.*kết nối', key):
resp = str(self.dev_count.get())
elif m := re.match(r'số seri của công tơ\s*(\d+)', key):
idx = int(m.group(1)) - 1
resp = self.sn_vars[idx].get() if 0 <= idx < len(self.sn_vars)
else ''
elif key in self.cmd_map:
resp = self.cmd_map[key]()
elif key == 'getconfig':
self.send_config()
continue
else:
continue
self.ser.write((resp + "\n").encode())
self.log(f"➡ {resp}")
except:
break
def on_close(self):
self.disconnect()
self.destroy()
if __name__ == '__main__':
app = SerialGUI()
app.mainloop()