采用snap7实现对PLC1200的I、Q、M、DB区域进行读写
强调:该部分的内容只在实际硬件上测试成功,采用PLCSIM造的假PLC通讯不上,后续有效的话再进行完善
1、PLC权限设置
安装的方式按照个人前一篇来,python-snap7的安装记录,出现意外再另寻方法吧,默认现在相关的软件已经安装完毕。
在建立通讯前需要再TIA内将PLC的读写权限给出,版本较低的1200 CPU没有下图的内容可以不设置,同样可以进行使用, 亲测有效
2、资料参考
参考学习中参考了相关的学习资料,这里一并给出以供参考
考虑下面个人给出了我自己的内容,这里只截取我需要的内容到下面作为参考
- PLC的读写主要是通过read_area和write_area两个函数实现,个人理解的思路就是read_area(读出来)—修改—write_area(写进去)
- snap7对函数的定义:read_area(self,area,dbnumber,start,size)以及write_area(self,area,dbnumber,start,data),既然是read_area和write_area,因此两个函数的核心都是对area进行读写操作,area用于区分I、Q、M、DB区域,具体选择如下(写过函数都知道,self这里是不用的)
areas = ADict({
'PE': 0x81, #input 输入区
'PA': 0x82, #output 输出区
'MK': 0x83, #bit memory 中间存储区(M区)
'DB': 0x84, #DB区
'CT': 0x1C, #counters
'TM': 0x1D, #Timers
})
dbnumber只有在对DB块使用时才有用,默认设置为0,start起始地址为Q0.4的这个0(也就是输出的第几个字节),size是选择读取的数据类型,见下图,(为什么选择地址这里没有说要给出Q0.4的4呢?是因为读取数据类型以byte起步,由参数size决定,要对位进行操作可以在读取后采用其他指令对具体某一位做读写)
注意:write_area的前几个参数与read_area一致,但最后一个参数为data,其实就是read_area读取的数据,(先有read再有write)
3. 读了再写就是写,只读不写就是读 ------- PLC的Q区域读写操作
3、采用snap7 对PLC的输出进行读写
学习的资料是用了两个.py文件,一个专门用来构建读取和改写的函数,另一个则是用使用构造的函数进行读写操作,比较推荐这种,下次要用直接调用,一行代码实现读写
首先是plc.py文件(名字自己取,改了的话import时记得换)
from snap7.util import * #对位操作的函数要导入该库
#定义的函数可直接对QX.X一个位进行操作
def Writeoutput(dev,byte,bit,cmd):
data=dev.read_area(0x82,0,byte,1)#0x82表示输出Q,byte表示起始地址(Q0),1表示类型为byte,cmd位置的值(True或False)
set_bool(data,byte,bit,cmd) #置Qbyte.bit(即Q0.4)为cmd(即True)
dev.write_area(0x82,0,byte,data)#同样,进行写数据操作
#该函数只进行了读操作
def Readoutput(dev,byte,bit):
data=dev.read_area(0x82,0,byte,1)
status=get_bool(data,byte,bit) #获取位状态
return status
其次是output.py文件(名字可以随便取)
import snap7
import plc as PLC #导入前面的plc.py文件(注意两个py文件在一个文件夹)
myplc=snap7.client.Client()
myplc.connect('192.168.0.1', rack=0,slot=1) #建立连接(相关信息去TIA看,IP,机架和插槽)
print(myplc.get_connected()) # 测试是否通讯成功
#使用plc.py里构建的两个函数(写、读)
PLC.Writeoutput(myplc,0,4,True) #该函数效果就是写Q0.4为True
status=PLC.Readoutput(myplc,0,4) #该函数效果就是读取Q0.4的值
print(status)
效果:
注意
- 两个py文件最好放在一个文件夹,或灵活运用python的import
- 上面的代码是复杂化一点的的,对位读写如果单纯的编个最简单的代码的话只需要在一个py文件里按照顺序:建立通讯——read-area——set_bool/get_bool——write_area(读的话都不用)就能够实现读和写操作
4、采用snap7对PLC的中间存储区进行读和写
上面是分两个py文件,现在是把读取和写入分开,都是无关紧要的简单变换
4.1读取
直接上程序
import snap7
from snap7.util import *
from snap7.snap7types import *
def ReadMemory(self,byte,bit,datatype):
result=self.read_area(0x83,0,byte,datatype)
if datatype==S7WLBit: #这里的datatype应该可以写数字(参照上面写入output),或者像这里直接写类型
return get_bool(result,0,bit)
if datatype==S7WLByte or datatype==S7WLWord:
return get_int(result,0)
if datatype==S7WLReal: #elif更好
return get_real(result,0)
if datatype==S7WLDWord:
return get_dword(result,0)
myplc=snap7.client.Client()
myplc.connect('192.168.0.1', rack=0,slot=1)
print('bool型',ReadMemory(myplc,10,0,S7WLBit))
print('byte型',ReadMemory(myplc,11,0,S7WLByte))
print('Word型',ReadMemory(myplc,12,0,S7WLWord))
print('Real型',ReadMemory(myplc,14,0,S7WLReal))
print('Dword型',ReadMemory(myplc,18,0,S7WLDWord))
对应的输出:(显示的都是十进制显示)
bool型 True
byte型 4864 #除以256才是实际值
Word型 33
Real型 23.12310028076172
Dword型 7
TIA的显示:
注意: 可以看到MB11是错的,因为byte只有8位,十六进制是0x13,输出的4864是0x1300,即16位,还不清楚有没有专门的读8位的get函数,先用除以256代替实际值,即4864/256=19
4.2 写入
同样直接上程序
import snap7
from snap7.util import *
from snap7.snap7types import *
def WriteMemory(self,byte,bit,datatype,value):
result=self.read_area(0x83,0,byte,datatype)
if datatype==S7WLBit:
set_bool(result,0,bit,value)
if datatype==S7WLByte or datatype==S7WLWord:
set_int(result,0,value)
if datatype==S7WLReal:
set_real(result,0,value)
if datatype==S7WLDWord:
set_dword(result,0,value)
self.write_area(0x83,0,byte,result)
myplc=snap7.client.Client()
myplc.connect('192.168.0.1', rack=0,slot=1)
WriteMemory(myplc,10,0,S7WLBit,False)
WriteMemory(myplc,11,0,S7WLByte,2048) #除以256才是传输的值(8)
WriteMemory(myplc,12,0,S7WLWord,32)
WriteMemory(myplc,14,0,S7WLReal,3.14159)
WriteMemory(myplc,18,0,S7WLDWord,1132818)
TIA的显示
与Q区域的读写一样,如果单纯想要实现读写效果的话,实际的操作会更加简单,这里为了做充分的示例做了一些额外的代码完善工作,自己用时,可以灵活删减与组合
5、采用snap7对PLC的输入区进行读
注意我用的DC/DC/DC没法直接修改输入,不知道继电器的行不行(可测),因此这里只有读,没有写
然后就是直接上程序
import snap7
from snap7.util import *
from snap7.snap7types import *
def Readoutput(dev,byte,bit):
data=dev.read_area(0x81,0,byte,S7WLBit) #S7WLBit可以用1代替
status=get_bool(data,byte,bit)
return status
myplc=snap7.client.Client()
myplc.connect('192.168.0.1', rack=0,slot=1)
status=Readoutput(myplc,0,0)
print(status) #输出False
6、采用snap7对PLC的DB数据块进行读和写
不管对DB干什么也要先有DB数据块,并且去掉块优化
6.1 DB块数据读取
对DB块的学习多花了一些时间,因为代码确实比较长,个人python基础又很差。关于读取DB数据块难点(其实也就是比前面难一点)在于是对DB块内的一块区域读取数据,相对于前面一个一个数据读取更麻烦。其主要的思路是要知道各个数据的起始位置,以及整个DB块的长度,然后逐个数据提取并输出。
先说我要读取的内容
可以看到内容包括:
1.DB块为DB10
2.数据名称,数据类型,数据偏移量
这两个信息是读取操作必须的信息,很重要
直接上代码(程序里面的解释应该比较详细了)
import snap7
from snap7.util import *
from snap7.snap7types import *
class DBObject(object): #空对象,用于后面__setattr__使用
pass
#offset是字典,其内容代表各类型数据所占字节长度
offset={"Bool":2,"Int":2,"Real":4,"Dint":4,"String":256} #实测所占长度准确
#db表示要读取的信息,由上图得到,中间用Tab隔开,非空格
db=\
"""
Temperature Real 0.0
Cold Bool 4.0
RPis_to_Buy Int 6.0
Db_test_String String 8.0
"""
#get_db_size用来获取DB块内数据长度,由最后一个数据起始地址和它的数据类型得到(这里是8.0和String,长度就是8+256=264),就这一个作用(苦笑)
def get_db_size(array,bytekey,datatypekey):#bytekey:地址 datatypekey:数据类型
seq,length=[x[bytekey] for x in array],[x[datatypekey] for x in array]
idx=seq.index(max(seq)) #index就来检索位置
lastByte=int(max(seq).split('.')[0])+(offset[length[idx]])
return lastByte #返回DB块内要读取的最末尾位置,这里是264
#DBRead才是用来读取数据的函数,length是获取的最大长度
def DBRead(myplc,db_num,length,items):
data=myplc.read_area(0x84,db_num,0,length)
obj=DBObject()
for item in items:
value=None
offset=int(item['bytebit'].split('.')[0]) #取数据起始位置
##读取各种数据类型的数据
if item['data']=='Real':
value=get_real(data,offset)
if item['data']=="Bool":
bit=int(item['bytebit'].split('.')[1])
value=get_bool(data,offset,bit)
if item['data']=="Int":
value=get_int(data,offset)
if item['data']=='String':
value=get_string(data,offset,256)
obj.__setattr__(item['name'],value)#构建obj.item['name']=value形式,方便后续输出
return obj
if __name__=="__main__":
myplc=snap7.client.Client()
myplc.connect('192.168.0.1', rack=0,slot=1)
#过滤器函数,把后面list内的东西放入前面的函数判断,生成返回为True的新list
itemlist=list(filter(lambda a:a!='',db.split('\n'))) #提取每行数据(一行一个)
#把每个数据的name(名字)、data(数据类型)bytebit(起始地址)归好类
items=[
{
"name":x.split( )[0], #参数名
"data":x.split( )[1], #数据类型
"bytebit":x.split( )[2] #数据起始地址
} for x in itemlist
]
#调用函数,先获取数据长度,再读取各个数据的值,放在对象实例里面obj
length=get_db_size(items,'bytebit','data')
meh=DBRead(myplc,10,length,items)
#输出各个数据
print('''
Cold:\t\t\t{}
Tempeature:\t\t{}
Rpis_to_Buy:\t\t{}
Db_test_String:\t{}
'''.format(meh.Cold,meh.Temperature,meh.RPis_to_Buy,meh.Db_test_String))
myplc.disconnect();
程序运行效果
简单说下程序结构,在函数之前是要用到的材料,空对象、字典、要查询的内容,然后是两个函数,第一个函数获取DB块内部数据的长度(第二个函数里用),第二个函数读取各个数据;然后是主函数,通讯自然不用说,主要还是把db=
“”"
Temperature Real 0.0
Cold Bool 4.0
RPis_to_Buy Int 6.0
Db_test_String String 8.0
“”"
类型的输入转变为
[{‘name’: ‘Temperature’, ‘data’: ‘Real’, ‘bytebit’: ‘0.0’},
{‘name’: ‘Cold’, ‘data’: ‘Bool’, ‘bytebit’: ‘4.0’},
{‘name’: ‘RPis_to_Buy’, ‘data’: ‘Int’, ‘bytebit’: ‘6.0’},
{‘name’: ‘Db_test_String’, ‘data’: ‘String’, ‘bytebit’: ‘8.0’}]
这种格式的数据,方便函数读取(方法用split)---------(自己直接输入下面这种类型的数据也行)
然后用上面两个函数得到相应的数据,最后print()就行
6.2 写入DB块(只改不加)
起始写入DB块完全可以参照上面的写入的格式去写,因此只需要把6.1内读取的部分程序改为写入就行了,再加上个write_area就行了
因此,修改的部分程序(仅DBRead函数)如下,其他部分与6.1程序完全一致,虽然读的部分有点累赘,稍微再改一点,就又能读,又能写了。
def DBRead(myplc,db_num,length,items):
data=myplc.read_area(0x84,db_num,0,length)
obj=DBObject()
for item in items:
value=None
offset=int(item['bytebit'].split('.')[0]) #取数据起始位置
##读取各种数据类型的数据
if item['data']=='Real':
value=get_real(data,offset)
set_real(data,offset,132.032)
if item['data']=="Bool":
bit=int(item['bytebit'].split('.')[1])
value=get_bool(data,offset,bit)
set_bool(data,offset,0,False)
if item['data']=="Int":
value=get_int(data,offset)
set_int(data,offset,312)
if item['data']=='String':
value=get_string(data,offset,256)
print(offset)
set_string(data,offset,'Who I am',256)
myplc.write_area(0x84,db_num,0,data)
obj.__setattr__(item['name'],value) #构建obj.item['name']=value形式,方便后续输出
return obj
结果如下
6.3 关于DB块读取的一个骚操作
以上所有的代码基本都是从别人的教程里面copy的,所以程序都会显得有点臃肿,毕竟是教程性质的,所以各个类型的都会牵扯一下,特别是DB块的读取,虽然简单,但是绕了弯差点没读懂。
在全部内容敲了一遍后加一个自己写的最简单的DB读取和写入,不写多,就一个简单的骚操作测试,当然对前面所有内容适用now:
import snap7
from snap7.util import *
from snap7.snap7types import *
myplc=snap7.client.Client()
myplc.connect('192.168.0.1', rack=0,slot=1)
data=myplc.read_area(0x84,10,0,256) #知道是DB10了
value=get_int(data,6) #已经知道6.0 是int型
print(value)
set_int(data,6,112)
myplc.write_area(0x84,10,0,data)
效果
这部分可以看出DB块读取的几个细节:
1.不一定要读取DB块的全部数据,仅一个数据也行
2.Read_area的size参数读取DB时不能超过实际DB块的最大值(这里是264),这就是为什么复杂的代码要专门获取这个值,但自己测试可以专门看着TIA给(真皮)
3.真的要简单实现效果是真的可以很简单的,核心就4行,还能读能写
1.前面的程序部分很多细节做了一些尝试,实际运用时可以根据各程序段的内容灵活修改
格外注意2.以上所有的代码copy下来运行时--“注意一下下缩进”--