Giới thiệu
Bài viết này hướng dẫn chi tiết cách tạo một ứng dụng Windows Desktop để kết nối với PLC Keyence KV Series (KV-8000, KV-7500, KV-5500, KV-5000, KV-3000) và đọc trạng thái của device DM100 qua giao thức MC Protocol.
Yêu cầu
- PLC Keyence KV Series (có hỗ trợ EtherNet/IP hoặc MC Protocol)
- Python 3.x đã cài đặt
- Thư viện: tkinter (có sẵn trong Python)
- Kết nối mạng giữa PC và PLC
Thông tin kỹ thuật Keyence KV Series
- Giao thức: MC Protocol (Mitsubishi Compatible)
- Port mặc định: 8501
- Device DM: Data Memory (16-bit word)
- Lệnh đọc: RD (Read Device)
- Các dòng hỗ trợ: KV-8000, KV-7500, KV-5500, KV-5000, KV-3000
Code mẫu Python
import tkinter as tk
from tkinter import ttk, messagebox
import socket
import threading
import time
class PLCMonitorApp:
def __init__(self, root):
self.root = root
self.root.title("PLC Keyence KV Series Monitor")
self.root.geometry("500x400")
self.plc_ip = tk.StringVar(value="192.168.0.1")
self.plc_port = tk.IntVar(value=8501)
self.device_address = tk.StringVar(value="DM100")
self.connected = False
self.socket = None
self.monitoring = False
self.create_widgets()
def create_widgets(self):
conn_frame = ttk.LabelFrame(self.root, text="Cấu hình kết nối", padding=10)
conn_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(conn_frame, text="PLC IP:").grid(row=0, column=0)
ttk.Entry(conn_frame, textvariable=self.plc_ip, width=15).grid(row=0, column=1, padx=5)
ttk.Label(conn_frame, text="Port:").grid(row=0, column=2)
ttk.Entry(conn_frame, textvariable=self.plc_port, width=8).grid(row=0, column=3, padx=5)
device_frame = ttk.LabelFrame(self.root, text="Device Settings", padding=10)
device_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(device_frame, text="Device:").grid(row=0, column=0)
ttk.Entry(device_frame, textvariable=self.device_address, width=10).grid(row=0, column=1, padx=5)
btn_frame = ttk.Frame(self.root)
btn_frame.pack(fill="x", padx=10, pady=5)
self.connect_btn = ttk.Button(btn_frame, text="Kết nối", command=self.toggle_connection)
self.connect_btn.pack(side="left", padx=5)
self.read_btn = ttk.Button(btn_frame, text="Đọc", command=self.read_once, state="disabled")
self.read_btn.pack(side="left", padx=5)
self.monitor_btn = ttk.Button(btn_frame, text="Monitor", command=self.toggle_monitor, state="disabled")
self.monitor_btn.pack(side="left", padx=5)
result_frame = ttk.LabelFrame(self.root, text="Kết quả", padding=10)
result_frame.pack(fill="both", expand=True, padx=10, pady=5)
self.result_text = tk.Text(result_frame, height=10)
self.result_text.pack(fill="both", expand=True)
self.status_var = tk.StringVar(value="Trạng thái: Chưa kết nối")
status_bar = ttk.Label(self.root, textvariable=self.status_var, relief="sunken")
status_bar.pack(fill="x", side="bottom")
def build_mc_protocol_frame(self, device):
if device.startswith("DM"):
dev_code = 0xA8
dev_addr = int(device[2:])
else:
dev_code = 0xA8
dev_addr = int(device)
frame = bytearray()
frame.extend([0x50, 0x00])
frame.extend([0x00, 0xFF, 0xFF, 0x03, 0x00])
data_length_pos = len(frame)
frame.extend([0x00, 0x00])
frame.extend([0x00, 0x10])
frame.extend([0x01, 0x04])
frame.extend([0x00, 0x00])
frame.append(dev_code)
frame.append(dev_addr & 0xFF)
frame.append((dev_addr >> 8) & 0xFF)
frame.append((dev_addr >> 16) & 0xFF)
frame.extend([0x01, 0x00])
data_length = len(frame) - data_length_pos - 2
frame[data_length_pos] = data_length & 0xFF
frame[data_length_pos + 1] = (data_length >> 8) & 0xFF
return bytes(frame)
def parse_response(self, data):
if len(data) < 11:
return None, "Response too short"
end_code = data[9] + (data[10] <= 13:
value = data[11] + (data[12] << 8)
return value, "OK"
return None, "No data"
def toggle_connection(self):
if not self.connected:
self.connect()
else:
self.disconnect()
def connect(self):
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(5)
self.socket.connect((self.plc_ip.get(), self.plc_port.get()))
self.connected = True
self.status_var.set(f"Đã kết nối {self.plc_ip.get()}")
self.connect_btn.config(text="Ngắt")
self.read_btn.config(state="normal")
self.monitor_btn.config(state="normal")
self.log("✅ Kết nối thành công!")
except Exception as e:
messagebox.showerror("Lỗi", str(e))
def disconnect(self):
self.monitoring = False
if self.socket:
self.socket.close()
self.connected = False
self.status_var.set("Chưa kết nối")
self.connect_btn.config(text="Kết nối")
self.read_btn.config(state="disabled")
self.monitor_btn.config(text="Monitor")
self.log("❌ Đã ngắt kết nối")
def read_once(self):
if not self.connected:
return
try:
device = self.device_address.get()
frame = self.build_mc_protocol_frame(device)
self.socket.send(frame)
response = self.socket.recv(1024)
value, msg = self.parse_response(response)
if value is not None:
self.log(f"📊 {device} = {value}")
else:
self.log(f"⚠️ {msg}")
except Exception as e:
self.log(f"❌ {str(e)}")
def toggle_monitor(self):
if self.monitoring:
self.monitoring = False
self.monitor_btn.config(text="Monitor")
self.log("⏹️ Đã dừng")
else:
self.monitoring = True
self.monitor_btn.config(text="Dừng")
self.log("▶️ Bắt đầu monitor...")
threading.Thread(target=self.monitor_loop, daemon=True).start()
def monitor_loop(self):
while self.monitoring and self.connected:
self.read_once()
time.sleep(1)
def log(self, message):
timestamp = time.strftime("%H:%M:%S")
self.result_text.insert("end", f"[{timestamp}] {message}\n")
self.result_text.see("end")
if __name__ == "__main__":
root = tk.Tk()
app = PLCMonitorApp(root)
root.mainloop()
Hướng dẫn chạy
- Lưu code vào file: plc_monitor.py
- Mở Command Prompt, chạy: python plc_monitor.py
- Nhập IP PLC (mặc định: 192.168.0.1)
- Nhấn “Kết nối” → “Đọc” hoặc “Monitor”
Cấu hình PLC Keyence
- Mở KV STUDIO → Unit Editor → Ethernet module
- Cấu hình IP: 192.168.0.1
- Port MC Protocol: 8501
- Download xuống PLC
Giải thích MC Protocol Frame
- 50 00: Subheader (Request)
- 00 FF FF 03 00: Network/PC/Module
- 01 04: Read command
- A8: DM device code
- Address: 3 bytes little-endian
Code dựa trên tài liệu Keyence KV Series. Tested với KV-7500 và KV-8000.
