Go简介

以下摘自百度百科

Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC(垃圾回收),结构形态及 CSP-style并发计算。

Go是一门小而精的编程语言,没有过多复杂的语法,却有着极高的性能,特别适合高并发的应用场景。Go的语法与C相近,同时也有与Python和JS等语言相似的简明性,使得拥有其它语言基础的的人能极快上手Go。即便没有基础,也无需花费太多的时间,因为Go本身就不是一门复杂且难以学习的语言。Go语言的通道等特性的存在,使得Go天生就支持高并发,这是Go最大的优点之一。


gos7简介

gos7是国外一名开发工程师根据Snap7,开发的Go专用的西门子PLC通讯库。它采用的依旧是S7通讯协议,使用TCP/IP通讯,传输指定的S7通讯报文,以实现数据的读取与写入。较之前两篇文章中介绍过的python-snap7和S7.NetPlus,gos7对PLC与编程语言之间的数据类型转换的支持更好,虽然依然有一点小bug,但是依旧是一个非常优秀的通讯库。

GitHub地址:GitHub - robinson/gos7: Implementation of Siemens S7 protocol in golang

目前并未找到gos7的介绍文档,很多地方需要结合源码查看并进行使用。

本文源码已上传至GitHub,项目地址如下:

https://github.com/XMNHCAS/GoS7Demo


安装gos7

Go安装第三方库与Python类似,可以自动安装也可以手动安装。此处我们使用自动安装。

先创建一个文件夹,用以收纳我们的项目文件。然后输入以下命令,创建go.mod文件。

go mod init 项目名称

项目名称可以根据喜好自行修改。此处示例使用的是VS Code编辑器,使用其它的编辑器的操作也基本一致。效果如下图所示。

然后输入以下指令,自动安装gos7。

go get github.com/robinson/gos7

运行结果如下:

可以看到我们的项目目录下多了一个go.sum文件,这里就安装完成了。再在目录下新建一个src文件夹,用以存放我们的代码文件。需要注意的是,Go在一个根目录下只允许存在一个main函数,所以需要多个main函数的情况下,我们就需要在src文件夹下创建多个文件夹用以存放。


创建连接

如果手头没有PLC,可以参考我写的这篇文章:C#使用S7NetPlus以及PLCSIM Advanced V3.0实现西门子PLC仿真通讯

使用PLCSIM Advanced可以仿真出PLC来进行通讯测试

gos7依旧是使用S7通讯实现数据的传输,PLC充当的是服务端的角色,所以首先需要为我们的程序先创建TCP客户端,以实现与PLC之间的连接。

 const (ipAddr = "192.168.10.230" //PLC IPrack   = 0                // PLC机架号slot   = 1                // PLC插槽号)//PLC tcp连接客户端handler := gos7.NewTCPClientHandler(ipAddr, rack, slot)//连接及读取超时handler.Timeout = 200 * time.Second//关闭连接超时handler.IdleTimeout = 200 * time.Second//打开连接handler.Connect()//函数退出时关闭连接defer handler.Close()

完成TCP客户端的创建并连接之后,可以使用 gos7.NewClient()创建PLC的对象,用以获取PLC的信息或者读写数据。

    //获取PLC对象client := gos7.NewClient(handler)//获取PLC运行状态client.PLCGetStatus()

完整代码如下:

package mainimport ("fmt""time""github.com/robinson/gos7"
)func main() {const (ipAddr = "192.168.10.230" //PLC IPrack   = 0                // PLC机架号slot   = 1                // PLC插槽号)//PLC tcp连接客户端handler := gos7.NewTCPClientHandler(ipAddr, rack, slot)//连接及读取超时handler.Timeout = 200 * time.Second//关闭连接超时handler.IdleTimeout = 200 * time.Second//打开连接handler.Connect()//函数退出时关闭连接defer handler.Close()//获取PLC对象client := gos7.NewClient(handler)//输出PLC运行状态fmt.Println(client.PLCGetStatus())
}

运行结果如下:

client.PLCGetStatus()会返回两个结果,第一个是PLC的运行状态码,第二个是错误信息。运行之后它返回了8和nil,表示状态码为8,无错误。查看源码的client.go文件,可以看到状态码对应如下:

 // PLC Statuss7CpuStatusUnknown = 0s7CpuStatusRun     = 8s7CpuStatusStop    = 4

0为未知状态,8为运行状态,4为停止状态。


读取数据

gos7读取数据的方式均为使用PLC对象的AGReadDB()方法,读取所有需要读取的数据的字节,然后再做解析。gos7已经内置了解析数据的方法,当然如果了解数据的存储结构的话,也可以进行手动解析。

PLC的数据如下图所示:

gos7内置方法解析

gos7提供了一个gos7.Helper的结构体(相当于类),里面集成了多种PLC数据类型的解析和转换的方法。此处根据我们需要读取的五种数据,我们使用对应的方法进行数据解析。不过对于bool、int和real类型,都是可以使用GetValueAt方法进行数据解析的。

需要注意的是,gos7内置方法是可以解析WString类型的,中文也是可以解析的,所以无需另外再自己写解析函数。

代码如下:

package mainimport ("fmt""time""github.com/robinson/gos7"
)type PlcData struct {boolValue    boolintValue     uint16realValue    float32stringValue  stringwstringValue string
}func main() {const (ipAddr = "192.168.10.230"rack   = 0slot   = 1)//PLC tcp连接客户端handler := gos7.NewTCPClientHandler(ipAddr, rack, slot)//连接及读取超时handler.Timeout = 200 * time.Second//关闭连接超时handler.IdleTimeout = 200 * time.Second//打开连接handler.Connect()//函数退出时关闭连接defer handler.Close()//获取PLC对象client := gos7.NewClient(handler)//DB号address := 10//起始地址start := 0//读取字节数size := 776//读写字节缓存区buffer := make([]byte, size)//读取字节client.AGReadDB(address, start, size, buffer)//gos7解析数据类var helper gos7.Helper//gos7内置方法解析数据var data PlcDatadata.boolValue = helper.GetBoolAt(buffer[0], 0)helper.GetValueAt(buffer[2:4], 0, &data.intValue)data.realValue = helper.GetRealAt(buffer[4:8], 0)data.stringValue = helper.GetStringAt(buffer[8:264], 0)data.wstringValue = helper.GetWStringAt(buffer[264:], 0)//输出数据fmt.Println(data)
}

运行结果如下:

可以看到数据被成功读取并正确解析。

手动解析

在理解PLC的数据存储方式的情况下,可以不依靠gos7提供的方法,而使用Go语言的标准函数进行数据解析。

bool:通过循环位操作来判断字节中的每一位的值,根据大小端的对应的规则,转换成bool数组后即可获取bool的值。

int:通过binary.BigEndian.Uint16()方法,可以将值的字节数组转换为对应的uint16。此处是默认int值为正数,所以使用uint16,即16位无符号整型。如果值可能为负数,则需要修改此方法。

real:通过binary.BigEndian.Uint32()获取其32位整型形式,然后再通过math.Float32frombits()转换为正确的浮点型值。

string:由于PLC中的string为ASCII编码,第一个字节为该变量的最大字符数,第二个字节为该变量的当前字符数,故根据第二个字节的值对字节数组进行切片,获取字符所在的字节数组,然后直接使用string()方法做类型转换,即可获取正确的string的值。

wstring:原理与string一致,但是wstring是双字节存储,所以最大字符数为第一二个字节,变量的当前字符数是第三四个字节,所以需要解析第二个字节,获取字符数,然后再从第五个字节开始进行字节数组切片,获取字符串的值。在PLC中,wstring的编码格式是16位的大端Unicode,Go是无法直接将该编码格式的字节数组转换为正确的字符串的,所以需要使用transform.Bytes(unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewDecoder(), 字符的字节数组)方法进行一次解码,然后再将转码之后的结果使用string()方法做类型转换,这样就可以得到正确的结果了。

package mainimport ("encoding/binary""fmt""math""time""github.com/robinson/gos7""golang.org/x/text/encoding/unicode""golang.org/x/text/transform"
)type PlcData struct {boolValue    boolintValue     uint16realValue    float32stringValue  stringwstringValue string
}func main() {const (ipAddr = "192.168.10.230"rack   = 0slot   = 1)//PLC tcp连接客户端handler := gos7.NewTCPClientHandler(ipAddr, rack, slot)//连接及读取超时handler.Timeout = 200 * time.Second//关闭连接超时handler.IdleTimeout = 200 * time.Second//打开连接handler.Connect()//函数退出时关闭连接defer handler.Close()//获取PLC对象client := gos7.NewClient(handler)//DB号address := 10//起始地址start := 0//读取字节数size := 776//读写字节缓存区buffer := make([]byte, size)//读取字节client.AGReadDB(address, start, size, buffer)var data PlcData//手动解析Booldata.boolValue = ByteToBool(buffer[0])[0]//手动解析Intdata.intValue = binary.BigEndian.Uint16(buffer[2:4])//手动解析Realdata.realValue = math.Float32frombits(binary.BigEndian.Uint32(buffer[4:8]))//手动解析StringstringPos := 8data.stringValue = string(buffer[stringPos+2 : stringPos+2+int(buffer[stringPos+1])])//手动解析WStringwstringPos := 264endPos := binary.BigEndian.Uint16(buffer[wstringPos+2 : wstringPos+4])res, _, _ := transform.Bytes(unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewDecoder(), buffer[wstringPos+4:wstringPos+4+int(endPos)*2])data.wstringValue = string(res)//输出数据fmt.Println(data)}//字节转bool数组(大端)
func ByteToBool(data byte) [8]bool {var res [8]boolfor i := 0; i < 8; i++ {res[i] = data&1 == 1data = data >> 1}return res
}

运行结果如下:


写入数据

gos7提供了逐个写入和批量写入的方法,可以根据具体的应用场景选择需要使用的方法。我们在做读取的时候,实际上是生成了请求报文,发送给PLC后,PLC根据我们发送的报文返回结果报文,最后我们得到的就是包含了需要读取的数据的字节数组。而当我们写入的时候,其实就是需要把我们的数据按照PLC指定的规则生成报文,发送过去。gos7也提供了生成写入数据的字节数组的方法。

逐个写入

使用gos7.Helper的对应的Set方法,可以获取指定数据类型的字节数组,然后通过PLC对象的AGWriteDB()传入对应的值并生成写入报文,发送给PLC。

gos7写入WString存在一个小bug,因为一个中文字符在Go中是占用三个字节的,而在其它的地方的基本都是只占用两个字节,在PLC中也是两个字节。而gos7在计算字符数的时候,用的是len()函数,它返回的字符的字节数,也就是说如果我们传入的是"中文"两个字,它计算出来的字符数却是6个。当我们用gos7的SetWStringAt生成的字节数组,把数据写入PLC之后,PLC会发现字符数是6,但实际上只传了两个,这时PLC就会在传入的字符后面补"$0000",来进行字符数的补全。

因为这个问题,WString我们需要自己重新写方法来生成值,即代码最下面的SetWStringAt方法。

具体代码如下:

package mainimport ("time""github.com/robinson/gos7"
)func main() {const (ipAddr = "192.168.10.230" //PLC IPrack   = 0                // PLC机架号slot   = 1                // PLC插槽号)//PLC tcp连接客户端handler := gos7.NewTCPClientHandler(ipAddr, rack, slot)//连接及读取超时handler.Timeout = 200 * time.Second//关闭连接超时handler.IdleTimeout = 200 * time.Second//打开连接handler.Connect()//函数退出时关闭连接defer handler.Close()//获取PLC对象client := gos7.NewClient(handler)//gos7解析数据类var helper gos7.Helper//DB号dbNo := 10//起始地址startAdr := 0//写入数据的字节二位数组buffers := [][]byte{make([]byte, 2),make([]byte, 2),make([]byte, 4),make([]byte, 256),make([]byte, 512),}//生成需要写入的变量的数组helper.SetBoolAt(buffers[0][0], 0, false)helper.SetValueAt(buffers[1], 0, uint16(100))helper.SetRealAt(buffers[2], 0, float32(66.6))helper.SetStringAt(buffers[3], 0, 254, "Hello Go")SetWStringAt(buffers[4], 0, "中文")//循环数据,逐个写入for _, v := range buffers {client.AGWriteDB(dbNo, startAdr, len(v), v)startAdr += len(v)}
}//获取WString的报文
func SetWStringAt(buffer []byte, pos int, value string) []byte {chars := []rune(value)slen := len(chars)var maxLen int = 254if maxLen < slen {maxLen = slen}var helper gos7.Helperhelper.SetValueAt(buffer, pos+0, int16(maxLen))helper.SetValueAt(buffer, pos+2, int16(slen))for i, c := range chars {if i >= maxLen {return buffer}helper.SetValueAt(buffer, pos+4+i*2, uint16(c))}return buffer
}

运行结果如下:

可以看到,数据被成功写入。

批量写入

gos7中提供了批量写入的方式。逐个写入会为每一次写入生成一条对应的写入请求报文,而批量写入则是会将需要写入的所有数据放到一条报文中,一次性发送。

批量写入主要使用S7DataItem类。其中Area为数据类型;WordLen为字长,基本默认为0x02即可;DBNumber为DB号;Start为起始地址;Amount为实际数据字节数;Data为需要写入的数据的字节数组。

由源码可以得知Area和WordLen的对应标识码:

 // Area IDs7areape = 0x81 //process inputss7areapa = 0x82 //process outputss7areamk = 0x83 //Merkerss7areadb = 0x84 //DBs7areact = 0x1C //counterss7areatm = 0x1D //timers// Word Lengths7wlbit     = 0x01 //Bit (inside a word)s7wlbyte    = 0x02 //Byte (8 bit)s7wlChar    = 0x03s7wlword    = 0x04 //Word (16 bit)s7wlint     = 0x05s7wldword   = 0x06 //Double Word (32 bit)s7wldint    = 0x07s7wlreal    = 0x08 //Real (32 bit float)s7wlcounter = 0x1C //Counter (16 bit)s7wltimer   = 0x1D //Timer (16 bit)

代码如下:

package mainimport ("time""github.com/robinson/gos7"
)func main() {const (ipAddr = "192.168.10.230" //PLC IPrack   = 0                // PLC机架号slot   = 1                // PLC插槽号)//PLC tcp连接客户端handler := gos7.NewTCPClientHandler(ipAddr, rack, slot)//连接及读取超时handler.Timeout = 200 * time.Second//关闭连接超时handler.IdleTimeout = 200 * time.Second//打开连接handler.Connect()//函数退出时关闭连接defer handler.Close()//获取PLC对象client := gos7.NewClient(handler)//gos7解析数据类var helper gos7.Helper//写入数据的字节二位数组buffers := [][]byte{make([]byte, 2),make([]byte, 2),make([]byte, 4),make([]byte, 256),make([]byte, 512),}//需要写入的字符串stringValue := "Hello World"wstringValue := "中国"//生成需要写入的变量的数组buffers[0][0] = helper.SetBoolAt(buffers[0][0], 0, true)helper.SetValueAt(buffers[1], 0, uint16(66))helper.SetRealAt(buffers[2], 0, float32(33.33))helper.SetStringAt(buffers[3], 0, 254, stringValue)SetWStringAt(buffers[4], 0, wstringValue)//获取批量写入的DataItemdatas := []gos7.S7DataItem{{Area:     0x84,WordLen:  0x02,DBNumber: 10,Start:    0,Amount:   1,Data:     buffers[0],},{Area:     0x84,WordLen:  0x02,DBNumber: 10,Start:    2,Amount:   2,Data:     buffers[1],},{Area:     0x84,WordLen:  0x02,DBNumber: 10,Start:    4,Amount:   4,Data:     buffers[2],},{Area:     0x84,WordLen:  0x02,DBNumber: 10,Start:    8,Amount:   len([]rune(stringValue)) + 2,Data:     buffers[3],},{Area:     0x84,WordLen:  0x02,DBNumber: 10,Start:    264,Amount:   len([]rune(wstringValue))*2 + 4,Data:     buffers[4],},}//批量写入数据client.AGWriteMulti(datas, len(datas))
}//获取WString的报文
func SetWStringAt(buffer []byte, pos int, value string) []byte {chars := []rune(value)slen := len(chars)var maxLen int = 254if maxLen < slen {maxLen = slen}var helper gos7.Helperhelper.SetValueAt(buffer, pos+0, int16(maxLen))helper.SetValueAt(buffer, pos+2, int16(slen))for i, c := range chars {if i >= maxLen {return buffer}helper.SetValueAt(buffer, pos+4+i*2, uint16(c))}return buffer
}

运行结果如下:


结尾

本文介绍了Go通过gos7实现西门子PLC的通讯。由于gos7使用者并不多,而且该项目发布时间也不是很长,所以它依然存在一些bug,希望作者后续会有更新,并发布完整的使用文档。

Go是一门优秀的后端语言,在一些小型的数据采集项目中,服务器直连PLC的情况下,是可以考虑使用Go来实现的。

Go使用gos7实现西门子PLC通讯相关推荐

  1. PC与西门子PLC通讯免费软件Libnodave

    软件介绍:PC与西门子PLC通讯免费软件Libnodave Introduction: Libnodave is a free communication library for Siemens S7 ...

  2. 上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,

    上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,A系列,Q系列,L系列,R系列,全系系列,各种串口和各种网口通讯,, ...

  3. 上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯

    上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,A系列,Q系列,L系列,R系列,全系系列,各种串口和各种网口通讯,, ...

  4. 上位机与PLC 通讯源码DEMO 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制

    上位机与PLC 通讯源码DEMO 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,A系列,Q系列,L系列,R系列,全系系列,各种串口和各种网口通 ...

  5. python与西门子PLC通讯

    python与西门子PLC通讯 安装Python-snap7 win+R打开"运行",输入cmd,确定后,进入DOS命令行终端,输入下面的命令: pip install pytho ...

  6. C#与西门子plc通讯上位机 c#软件 工控软件

    C#与西门子plc通讯上位机 c#软件 工控软件 1.该程可以实现CSharp与西门子全系列plc(200,200smart,300,1200,1500)的以太网s7通讯,通讯传输快稳定. 2.该程序 ...

  7. jetson nano上编译与使用西门子PLC通讯库snap7

    文章目录 一.西门子snap7介绍 二.西门子S7通讯介绍 三.jetson nano编译snap7库 四.Qt Cmake导入snap7库 五.snap7主要函数说明 1.与PLC建立连接 2.读写 ...

  8. 威纶和s7200通讯线_PLC通讯,西门子plc通讯知识汇总学习

    (一)西门子 200 plc 使用 MPI 协议与组态王进行通讯时需要哪些设置? 1)在运行组态王的机器上需要安装西门子公司提供的 STEP7 Microwin 3.2 的编程软件,我们的驱动需要调用 ...

  9. c#与汇川机器人通讯_C#与西门子PLC通讯

    S7Net 如下图,ip="192.168.0.5". 如下图,rack为0,slot为1. public Plc(CpuType cpu, string ip, short ra ...

  10. C#中使用S7.net与西门子PLC通讯

    最近因为工作的原因用到了西门子PLC,在使用过程中一直在思考上位机和PLC的通讯问题,后来上网查了一下,找到了一个专门针对S7开发的一个.net库–<S7netPlus>,PLC通讯方法比 ...

最新文章

  1. 【AIX 命令学习】创建逻辑卷!
  2. py文件转exe时包含paramiko模块出错解决方法
  3. 按汇总分组/多维数据集
  4. Linux C 函数指针应用---回调函数
  5. [No0000E0]批量打开当前路径下的文件
  6. PHP书写规范 PHP Coding Standard
  7. 「陶哲軒實分析」 習題 3.5.1
  8. 【亲测】Ripro子主题美化X系列主题(夏系列)-开源未加密
  9. 百度文库复制内容,留作笔记
  10. 2招PDF去密码,秒学秒懂秒用上!
  11. 数据结构实验报告:顺序表基本操作的实现
  12. 网络安全学习笔记-入侵检测系统IDS
  13. 机器学习基石1 学习笔记
  14. seo软件优化工具软件-免费seo软件优化工具以及免费排名软件下载
  15. [网鼎杯 2018]Fakebook
  16. 导出DNS服务器上的记录
  17. 解决python.exe 无法找到程序入口,无法定位程序输入点
  18. Unity3d搭建HTTP弱联网的服务器搭建及客户端编写(一)之java服务器
  19. fastadmin 使用switch 点击修改无反应 提示“未更新任何行”
  20. CSS:focus-visible伪类真的太6了!

热门文章

  1. MMO 游戏中使用多核
  2. 计算机正在获取ip,电脑频繁显示网络电缆已拔出和正在获取IP怎么办
  3. python面向对象2--综合案例:烤地瓜、搬家具、单继承、多继承
  4. 苹果手机锁屏后无线重新连接服务器,iphone11锁屏自动断开wifi怎么办 苹果11手机热点自动断开解决方法...
  5. 计算机毕业设计HTML+CSS+JavaScript仿大型购物商城(1页)
  6. bouncing results问题
  7. python lncrna_一文解决TCGA任意肿瘤的差异lncRNA,miRNA,mRNA
  8. 华为2022年度伙伴奖项正式揭晓!恒驰信息荣获华为云GrowCloud优秀解决方案提供商奖
  9. RAP2 详细部署、操作指南
  10. V831基础-摄像头使用