0% found this document useful (0 votes)
26 views5 pages

Apptk 002

The document outlines a Python application that creates a graphical user interface (GUI) for configuring and managing a serial connection to an E-well Gateway device. It includes features for setting WiFi and MQTT configurations, connecting to the device, flashing firmware, and logging data. The application utilizes the Tkinter library for the GUI and the PySerial library for serial communication.

Uploaded by

namkaka123306
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views5 pages

Apptk 002

The document outlines a Python application that creates a graphical user interface (GUI) for configuring and managing a serial connection to an E-well Gateway device. It includes features for setting WiFi and MQTT configurations, connecting to the device, flashing firmware, and logging data. The application utilizes the Tkinter library for the GUI and the PySerial library for serial communication.

Uploaded by

namkaka123306
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

import threading

import serial
import serial.tools.list_ports
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import time
import json
import re
import os
import sys
import tkinter.font as tkfont
import subprocess

CONFIG_FILE = 'config.json'

class SerialGUI(tk.Tk):
def __init__(self):
super().__init__()
# --- Phóng to font hệ thống thêm 20% ---
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)

# Load config
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
saved = json.load(f)
except:
saved = {}
else:
saved = {}

# Variables
self.wifi_ssid = tk.StringVar(value=saved.get('ssid','CTY VTE'))
self.wifi_pass = tk.StringVar(value=saved.get('wifi_pass','01011984'))
self.username = tk.StringVar(value=saved.get('username','adminmqtt'))
self.user_pass = tk.StringVar(value=saved.get('user_pass','Sys123456'))
self.mqtt_broker =
tk.StringVar(value=saved.get('mqtt_broker','160.30.136.224'))
self.mqtt_port = tk.IntVar(value=saved.get('mqtt_port',1883))
self.gateway_sn = tk.StringVar(value=saved.get('gateway_sn',''))
self.dev_count = tk.IntVar(value=saved.get('devices',1))
self.sn_vars = []

# Command map for auto replies


self.cmd_map = {
'tên wifi': lambda: self.wifi_ssid.get(),
'mật khẩu wifi': lambda: self.wifi_pass.get(),
'sever mqtt': lambda: self.mqtt_broker.get(),
'port': lambda: str(self.mqtt_port.get()),
'username': lambda: self.username.get(),
'password': lambda: self.user_pass.get(),
'nhập seri của gateway': lambda: self.gateway_sn.get(),
'nhập số công tơ kết nối (1-255)': lambda: str(self.dev_count.get()),
'seri': lambda: ",".join(v.get() for v in self.sn_vars)
}

# Serial
self.ser = None
self._stop_read = threading.Event()

# Layout
pw = ttk.Panedwindow(self, orient='horizontal')
pw.pack(fill='both', expand=True)

# Left pane: config


left = ttk.Labelframe(pw, text="Cấu hình & Thiết bị")
pw.add(left, weight=1)
canvas = tk.Canvas(left)
vsb = ttk.Scrollbar(left, orient='vertical', command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side='right', fill='y')
canvas.pack(side='left', fill='both', expand=True)
frame_cfg = ttk.Frame(canvas)
frame_cfg.bind('<Configure>', lambda e:
canvas.configure(scrollregion=canvas.bbox('all')))
canvas.create_window((0,0), window=frame_cfg, anchor='nw')

fields = [
("WiFi SSID:", self.wifi_ssid),
("WiFi Pass:", self.wifi_pass),
("MQTT Broker:", self.mqtt_broker),
("MQTT Port:", self.mqtt_port),
("Username:", self.username),
("Password:", self.user_pass)
]
for i,(lbl,var) in enumerate(fields):
ttk.Label(frame_cfg, text=lbl).grid(row=i, column=0, padx=8, pady=4,
sticky='e')
width = 30 if isinstance(var,tk.StringVar) else 10
ttk.Entry(frame_cfg, textvariable=var, width=width).grid(row=i,
column=1, padx=8, pady=4, sticky='w')

row0 = len(fields)
ttk.Label(frame_cfg, text="SN Gateway:").grid(row=row0, column=0, padx=8,
pady=4, sticky='e')
ttk.Entry(frame_cfg, textvariable=self.gateway_sn, width=30).grid(row=row0,
column=1, padx=8, pady=4, sticky='w')
ttk.Label(frame_cfg, text="Số thiết bị:").grid(row=row0+1, column=0,
padx=8, pady=4, 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=row0+1, column=1, padx=8, pady=4, sticky='w')
self.sn_frame = ttk.Frame(frame_cfg)
self.sn_frame.grid(row=row0+2, column=0, columnspan=2, pady=8)
self.update_sn_entries()

# Right pane: serial + log + actions


right = ttk.Frame(pw)
pw.add(right, weight=2)

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)

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',12))
self.txt_log.pack(fill='both', expand=True, padx=6, pady=6)

act = ttk.Frame(right)
act.pack(fill='x', padx=12, pady=6)
self.btn_flash = ttk.Button(act, text="Nạp Firmware",
command=self.flash_firmware, state='disabled')
self.btn_flash.pack(side='left', padx=(0,12))
self.btn_clear_data = ttk.Button(act, text="Xóa dữ liệu",
command=self.clear_data, state='disabled')
self.btn_clear_data.pack(side='left', padx=(0,12))
self.btn_save = ttk.Button(act, text="Lưu config",
command=self.save_config, state='disabled')
self.btn_save.pack(side='left', padx=(0,12))
self.btn_clear_log = ttk.Button(act, text="Xóa log",
command=self.clear_log, state='disabled')
self.btn_clear_log.pack(side='left')

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()
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=30).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 COM 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_flash,self.btn_clear_data,self.btn_save,self.btn_clear_log,self.btn_disco
nnect): 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_flash,self.btn_clear_data,self.btn_save,self.btn_clear_log,self.btn_disco
nnect): 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: self.ser.write(("Đang xóa dữ liệu trước đó vui lòng không thao
tác thêm ...\n").encode()); self.log("Đang xóa dữ liệu trước đó vui lòng không thao
tác thêm ...")

def save_config(self):
if self.ser and self.ser.is_open: self.ser.write(("y\n").encode());
self.log("➡ Gửi 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]}
with open(CONFIG_FILE,'w',encoding='utf-8') as f:
json.dump(cfg,f,ensure_ascii=False,indent=2)

def clear_log(self): self.txt_log.delete('1.0',tk.END)

def flash_firmware(self):
if self.ser and self.ser.is_open: self._stop_read.set(); self.ser.close();
self.log("✖ Đã đóng COM để nạp firmware")
choice = messagebox.askquestion("Firmware","Chọn thư mục chứa các file .bin
(Yes) hoặc file .bin đơn (No)?")
if choice=='yes':
folder = filedialog.askdirectory(title="Chọn thư mục chứa các
file .bin")
if not folder: return
bl=os.path.join(folder,'bootloader.bin');
part=os.path.join(folder,'partitions.bin')
apps=[f for f in os.listdir(folder) if f.endswith('.bin') and f not
in('bootloader.bin','partitions.bin')]
if not apps: messagebox.showerror("Lỗi nạp","Không tìm thấy firmware
ứng dụng");return
appf=os.path.join(folder,apps[0]);
offsets=['0x1000',bl,'0x8000',part,'0x10000',appf]
else:
path=filedialog.askopenfilename(title="Chọn file
firmware",filetypes=[("Firmware files",("*.ino.bin","*.bin","*.bin")),("All
files","*.*")],defaultextension=".bin")
if not path:return
offsets=['0x10000',path]
port=self.cmb_port.get()
if not port: messagebox.showwarning("Thiếu COM","Chọn cổng COM trước khi
nạp firmware!");return
def _run_flash():
self.log("Bắt đầu nạp firmware với python -m esptool...")
cmd=[sys.executable,'-m','esptool','--chip','esp32','--port',port,'--
baud','921600','write_flash','-z']+offsets
try:
proc=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal
_newlines=True)
except FileNotFoundError: messagebox.showerror("Lỗi nạp","Không tìm
thấy esptool. Hãy pip install esptool");return
for ln in proc.stdout: self.log(ln.rstrip())
rc=proc.wait()
if rc==0: self.log("✔ Nạp firmware thành
công");messagebox.showinfo("Hoàn thành","Nạp firmware thành công!")
else: self.log(f"✖ Nạp firmware thất bại (code
{rc})");messagebox.showerror("Lỗi nạp",f"Mã trả về {rc}")
threading.Thread(target=_run_flash,daemon=True).start()

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)
key=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 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 send_config(self):

cfg={'ssid':self.wifi_ssid.get(),'wifi_pass':self.wifi_pass.get(),'username':self.u
sername.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()); self.log(f"➡ Gửi config: {msg}")

def log(self,msg): self.txt_log.insert('end',msg+'\n'); self.txt_log.see('end')


def on_close(self): self.disconnect(); self.destroy()

if __name__=='__main__': app=SerialGUI(); app.mainloop()

You might also like