一、三菱FX5U 从站设置
1. 打开GX works3 软件
2. 新建项目
3.按向导流程指示设置modbus-tcp从站功能
4.下载模块参数到PLC,并断电重启PLC。
注意:部分PLC会发生模块参数报警,需要PLC做固件升级。
二、PC端设置
1. pip install modbus-tk,struct
2. main.py
import modbus_tk
import modbus_tk.defines as cst
import modbus_tk.modbus_tcp as modbus_tcp
from shuJuZhuangHuang import WriteDint as wd
import os
import tkinter as tk
import tkinter.font as tkFont
import tkinter.ttk as ttk
import csv
from socket import *
import sys
import threading
import time,random,queue
import pickle
import re
from configparser import ConfigParser
logger = modbus_tk.utils.create_logger("console")
# 读取配置文件
cfg = ConfigParser()
cfg.read('config.ini')
SERVER= cfg.get('Server','IP')
PORT= cfg.getint('Server','PORT')
MACROFILE = cfg.get('File','macroFile')
MACROCOPY = cfg.get('File','macroCopy')
timeSave = os.path.getmtime(MACROFILE)
# 打包函数
def thread_it(func,*args):
'''将函数打包进程'''
# 创建进程
t = threading.Thread(target=func,args=args)
# 守护进程
t.setDaemon(True)
# 启动
t.start()
class GuiPart():
def __init__(self):
self.threadFlg1 = True
self.caiLiao = 0
self.jiaGongZongShu = 0
self.gongJianChanDu = 0
self.qiShiWeiZhi1 = 0
self.jieShuWeiZhi1 = 0
self.qiShiWeiZhi2 = 0
self.jieShuWeiZhi2 = 0
self.qiShiWeiZhi3 = 0
self.jieShuWeiZhi3 = 0
self.qiShiWeiZhi4 = 0
self.jieShuWeiZhi4 = 0
self.tuiHuoCiShu = 0
self.xieRuFlg = 0 # 写入允许-0 禁止-1
# 连接MODBUS TCP从机
try:
master = modbus_tcp.TcpMaster(host=SERVER,port=PORT)
master.set_timeout(5.0)
self.master = master
except modbus_tk.modbus.ModbusError as e:
logger.error("%s- Code=%d" % (e, e.get_exception_code()))
thread_it(self.readMacro)
self.guiProcess()
# 调整屏幕
def center_window(self,root,w,h):
'''
窗口居于屏幕中央
:param root: root
:param w: 窗口宽度
:param h: 窗口高度
:return:
'''
# 获取屏幕 宽、高
ws = root.winfo_screenwidth()
hs = root.winfo_screenheight()
# 计算x、y位置
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
root.geometry('%dx%d+%d+%d' %(w,h,x,y))
def send(self):
try:
# # 写寄存器起始地址为500的保持寄存器,操作寄存器个数为1
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 500 , output_value=[self.caiLiao]))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 604, output_value=[self.jiaGongZongShu]))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 608, output_value=wd(self.gongJianChanDu)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4606, output_value=wd(self.qiShiWeiZhi1)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4608, output_value=wd(self.jieShuWeiZhi1)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4610, output_value=wd(self.qiShiWeiZhi2)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4612, output_value=wd(self.jieShuWeiZhi2)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4614, output_value=wd(self.qiShiWeiZhi3)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4616, output_value=wd(self.jieShuWeiZhi3)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4618, output_value=wd(self.qiShiWeiZhi4)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4620, output_value=wd(self.jieShuWeiZhi4)))
logger.info(self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 4622, output_value=[self.tuiHuoCiShu]))
except modbus_tk.modbus.ModbusError as e:
logger.error("%s- Code=%d" % (e, e.get_exception_code()))
def reset(self):
print('reset')
self.threadFlg1 = True
thread_it(self.readMacro)
def readMacro(self):
global timeSave
while self.threadFlg1:
time.sleep(0.2)
# 读取PLC M4606 写入标志
try:
self.xieRuFlg = self.master.execute(2, cst.READ_COILS, 9228, 1)[0]
except modbus_tk.modbus.ModbusError as e:
logger.error("%s- Code=%d" % (e, e.get_exception_code()))
"""
# print("flg: " )
self.xieRuFlg = 1
print(self.xieRuFlg)
"""
# 如果写入标志==0可以写入,否则禁止更新。
if self.xieRuFlg == 0:
timeLast = os.path.getmtime(MACROFILE)
# print(timeLast,' > ',timeSave)
if timeLast > timeSave:
# print('time change')
timeSave = timeLast
os.system(r'copy %s %s'%(MACROFILE,MACROCOPY))
f = open (MACROCOPY,'r')
fileLines = f.readlines()
f.close()
# 读取数据
for item in fileLines:
patt = r'(\d+)\D+?((-)?\d+\.?\d{0,3})' # 匹配 500 = 34 500=34 500 = 34. 500 =34.5 500 = -34.565 不完全匹配 34.34352345
m = re.match(patt,item)
if m:
shapu = m.group(1)
data = m.group(2)
# 读取变量
if shapu == '500':
self.caiLiao = int(data)
elif shapu == '501':
self.jiaGongZongShu = int(data)
elif shapu == '502':
self.gongJianChanDu = int(float (data) * 10000)
elif shapu == '503':
self.qiShiWeiZhi1 = int(float (data) * 10000)
elif shapu == '504':
self.jieShuWeiZhi1 =int(float (data) * 10000)
elif shapu == '505':
self.qiShiWeiZhi2 =int(float (data) * 10000)
elif shapu == '506':
self.jieShuWeiZhi2 =int(float (data) * 10000)
elif shapu == '507':
self.qiShiWeiZhi3 =int(float (data) * 10000)
elif shapu == '508':
self.jieShuWeiZhi3 =int(float (data) * 10000)
elif shapu == '509':
self.qiShiWeiZhi4 =int(float (data) * 10000)
elif shapu == '510':
self.jieShuWeiZhi4 =int(float (data) * 10000)
elif shapu == '511':
self.tuiHuoCiShu = int(data)
self.send()
self.l_0_state.configure(bg = 'green')
self.varState.set("数据已更新!")
else:
#设置标签为红色
# print("help")
self.l_0_state.configure(bg = 'red')
self.varState.set("设备运行中,禁止写入。设备停止后自动更新数据,请注意!")
self.threadFlg1 = False
# 定义Gui界面
def guiProcess(self):
'''
GUI主界面
:param
:retuen
'''
root=tk.Tk()
self.root = root
# 设置窗口位置
root.title('********* V1')
self.center_window(root,400,120)
root.resizable(0,0)# 窗体大小可调整,分别表示x、y方向的可变性
# 设置字体
ft20 =tkFont.Font(family = 'Fixdsys',size = 20,weight = tkFont.BOLD)
ft30 =tkFont.Font(family = 'Fixdsys',size = 30,weight = tkFont.BOLD)
ft40 =tkFont.Font(family = 'Fixdsys',size = 40,weight = tkFont.BOLD)
ft50 =tkFont.Font(family = 'Fixdsys',size = 50,weight = tkFont.BOLD)
ft55 =tkFont.Font(family = 'Fixdsys',size = 55,weight = tkFont.BOLD)
ft80 =tkFont.Font(family = 'Fixdsys',size = 80,weight = tkFont.BOLD)
# 设置标签窗体
labelFm1= tk.LabelFrame(root)
labelFm1.pack(padx=5,pady=5,side=tk.TOP,fill=tk.X)
self.labelFm1 = labelFm1
# labelFm1
self.varState = tk.StringVar()
self.varState.set('*'*10)
l_0_state = tk.Label(labelFm1,textvariable =self.varState )
l_0_state.grid(row=0,column=0,padx=5,pady=5)
self.l_0_state = l_0_state
self.l_0_state.configure(bg = 'green')
button1 = tk.Button(labelFm1,text= "RESET" ,font=ft20)
button1.grid(row=3,column=0,padx = 5,pady=5)
self.button1 = button1
# 定义事件响应
button1.configure(command=self.reset) # 系统复位
root.mainloop()
self.threadFlg1 = False
print('self.threadFlg1 = ',self.threadFlg1)
if __name__=='__main__':
ui = GuiPart()
3. shuJuZhuangHuang.py 作为数据转换模块使用
import struct
def ReadFloat(*args,reverse=False):
for n,m in args:
n,m = '%04x'%n,'%04x'%m
if reverse:
v = n + m
else:
v = m + n
y_bytes = bytes.fromhex(v)
y = struct.unpack('!f',y_bytes)[0]
y = round(y,6)
return y
def WriteFloat(value,reverse=False):
y_bytes = struct.pack('!f',value)
# y_hex = bytes.hex(y_bytes)
y_hex = ''.join(['%02x' % i for i in y_bytes])
n,m = y_hex[:-4],y_hex[-4:]
n,m = int(n,16),int(m,16)
if reverse:
v = [n,m]
else:
v = [m,n]
return v
def ReadDint(*args,reverse=False):
for n,m in args:
n,m = '%04x'%n,'%04x'%m
if reverse:
v = n + m
else:
v = m + n
y_bytes = bytes.fromhex(v)
y = struct.unpack('!i',y_bytes)[0]
return y
def WriteDint(value,reverse=False):
y_bytes = struct.pack('!i',value)
# y_hex = bytes.hex(y_bytes)
y_hex = ''.join(['%02x' % i for i in y_bytes])
n,m = y_hex[:-4],y_hex[-4:]
n,m = int(n,16),int(m,16)
if reverse:
v = [n,m]
else:
v = [m,n]
return v
if __name__ == "__main__":
print(ReadFloat((15729,16458)))
print(WriteFloat(3.16))
print(ReadDint((1734,6970)))
print(WriteDint(456787654))
4. 添加配置文件 config.ini
;config.ini
[Server]
IP=192.168.3.39
PORT=502
[File]
macroFile=c:\\macro\\macro.txt
macroCopy=macro.txt
5.添加macro.txt文本
500 = 2
501 = 3
502 = 40.03
503 = 5.5
504 = 6.6
505 = 7.8
506 = 8.8
507 = 9.6
508 = 8.7
509 = 7.7
510 = 6.7
511 = 3
6.添加指令定义
#modbus exception codes
ILLEGAL_FUNCTION = 1
ILLEGAL_DATA_ADDRESS = 2
ILLEGAL_DATA_VALUE = 3
SLAVE_DEVICE_FAILURE = 4
COMMAND_ACKNOWLEDGE = 5
SLAVE_DEVICE_BUSY = 6
MEMORY_PARITY_ERROR = 8
#supported modbus functions
RAW = 0
READ_COILS = 1
READ_DISCRETE_INPUTS = 2
READ_HOLDING_REGISTERS = 3
READ_INPUT_REGISTERS = 4
WRITE_SINGLE_COIL = 5
WRITE_SINGLE_REGISTER = 6
READ_EXCEPTION_STATUS = 7
DIAGNOSTIC = 8
REPORT_SLAVE_ID = 17
WRITE_MULTIPLE_COILS = 15
WRITE_MULTIPLE_REGISTERS = 16
READ_FILE_RECORD = 20
READ_WRITE_MULTIPLE_REGISTERS = 23
DEVICE_INFO = 43
#supported block types
COILS = 1
DISCRETE_INPUTS = 2
HOLDING_REGISTERS = 3
ANALOG_INPUTS = 4