Hướng dẫn tạo Windows Desktop App kết nối PLC Keyence KV Series

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

  1. Lưu code vào file: plc_monitor.py
  2. Mở Command Prompt, chạy: python plc_monitor.py
  3. Nhập IP PLC (mặc định: 192.168.0.1)
  4. 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.

Chuyên mục: Blog
Bài viết đã được tạo 7

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Bài liên quan

Bắt đầu nhập từ khoá bên trên và nhấp enter để tìm kiếm. Nhấn ESC để huỷ.

Trở lên trên