草庐IT

golang + modbus tcp

远方V3 2023-03-28 原文

基本原理

Modbus是PLC常用的通讯协议,经常用于与HMI通信。通过对此协议的分析,可以如同三菱MC协议一样,利用来与PC结合,发挥更大的作用。

Modbus 是一个应用层的通讯协议,位于 OSI 的第七层,在总线或者网络上的不同设备之间的,通过 客户端/服务端 的方式通讯,默认使用502端口进行通讯。

通讯示例:

// Remember: Big-endian values!

var responseADU = []byte{

	// MBAP HEADER:
	0x00, 0xFF, // Tx ID #255, typically an incremental value
	0x00, 0x00, // Protocol ID, always 0
	0x00, 0x0B, // Length of the following data (8+3=11)
	0x00, // Unit identifier, 0 unless a Gateway is used

	// PROTOCOL DATA UNIT:
	0x03,       // Function Code 03: Read Holding Registers
	0x08,       // Byte count of the following data (4*2=8)
	0x00, 0x0A, // First register; value 10
	0x0A, 0x00, // Second register; value 2560
	0xFF, 0xFF, // Third register; value 65535 or -1 when signed.
	0x00, 0x01, // Fourth register; value 1
}

功能码说明

代码 中文名称 位操作/字操作 操作数量
01h 读线圈状态 位操作 单个或多个
02h 读离散输入状态(只能读到0或1) 位操作 单个或多个
03h 读保持寄存器(保持寄存器可以通过06h功能写入) 字操作 单个或多个
04h 读输入寄存器(输入寄存器只能读取,不能通过06h功能写入) 字操作 单个或多个
05h 写单个线圈(线圈表示用来控制输出IO控制) 位操作 单个
06h 写单个保持寄存器 字操作 单个
0Fh 写多个线圈 位操作 多个
10h 写多个保持寄存器 字操作 多个

调试工具

Modbus Slave version 6.0.2

官方工具,不太好用,检测地址长度不能超过125,无法正确显示高低位转换的数据
链接: https://pan.baidu.com/s/19tEx6KzM0bjbCv342cA3zg
提取码: z44q

Modbus/TCP Master

优点:可以同时显示多种类型的数据比较方便
缺点:检测地址长度不能超过125;无法正确显示高低位转换的数据;无法自动刷新

ModScan 32 version 8.A00-10

应该是最好用的工具了,长度没有限制,可以自动刷新,可以自定义数据的类型,并且可以处理高低位转换的场景
选择 connection -> connect

录入 modbus tcp server 的地址和端口号

setup -> Display Options -> floating point -> 选择高位优先,还是低位优先

ngrok

在车间连接外网的机器上,把502端口映射到外网,方便远程调试。
官网 (https://dashboard.ngrok.com/get-started/setup) 注册账号,下载客户端

# 进入 rgrok 命令行工具所在目录
# 配置token
ngrok config add-authtoken {ngrok token} 
# 映射modbus tcp server 到外网
ngrok tcp {modbus tcp server ip}:502

高低位的问题

在plc中,有的是低位优先,也有的是高位优先,比如:
如果一个Int类型数组,占用4个字节,4个字节顺序为ABCD,那么采用big-endian大端字节顺序,那么在内存中即为ABCD,如果采用small-endian小端字节顺序,那么在内存中存储即为DCBA,但是在实际应用中,还有可能出现BADC或者CDAB的情况,因此我们在大小端的基础上做了一下扩展,定义了4种不同字节顺序,采用枚举类型表示,代码如下所示:

            ABCD = 0, // 大端形式
            BADC = 1, // 单字反转
            CDAB = 2, // 双字反转
            DCBA = 3, // 小端形式

示例程序 golang

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "testing"
    "time"

    "github.com/simonvetter/modbus"
)

func Test_Modbus(t *testing.T) {
    var client *modbus.ModbusClient
    var err error

    // for a TCP endpoint
    // (see examples/tls_client.go for TLS usage and options)
    client, err = modbus.NewClient(&modbus.ClientConfiguration{
        URL:     "tcp://ip:port", // 可以直接使用ngrok映射到外网的地址
        Timeout: 1 * time.Second,
    })
    // note: use udp:// for modbus TCP over UDP
    if err != nil {
        fmt.Println(err.Error())
    }

    err = client.Open()
    if err != nil {
        fmt.Println(err.Error())
    }

    // uint
    fmt.Println("uint read and write")
    for i := 0; i <= 7; i++ {
        address := uint16(i)
        value := uint16(1)
        v, err := client.ReadRegister(address, modbus.HOLDING_REGISTER)
        if err != nil {
            fmt.Println(err.Error())
        }
        fmt.Println("address:", address, ",old value:", v)
        err = client.WriteRegister(address, value)
        if err != nil {
            fmt.Println(err.Error())
        }
        v, err = client.ReadRegister(address, modbus.HOLDING_REGISTER)
        if err != nil {
            fmt.Println(err.Error())
        }
        fmt.Println("address:", address, ",new value:", v)
    }

    // float
    fmt.Println("float read and write")
    bytes, err := client.ReadBytes(6, 4, modbus.HOLDING_REGISTER)
    if err != nil {
        fmt.Println(err.Error())
    }
    f, _ := bytesToFloat32(bytes)
    fmt.Println("old value:", f)
    err = client.WriteBytes(6, float32ToBytes(123.456))
    if err != nil {
        fmt.Println(err.Error())
    }
    bytes, err = client.ReadBytes(6, 4, modbus.HOLDING_REGISTER)
    if err != nil {
        fmt.Println(err.Error())
    }
    f, _ = bytesToFloat32(bytes)
    fmt.Println("new value:", f)

}

参考资料

modbus tcp: https://www.ad.siemens.com.cn/productportal/Prods/published/Comm/Comm_9.1/Comm_9.1.html
golang modbus: https://github.com/goburrow/modbus
modbus server:https://blog.csdn.net/weixin_42330983/article/details/124860023
Modscan32 https://www.sohu.com/a/443539826_651846
https://github.com/ffffffff0x/1earn/blob/master/1earn/Security/ICS/实验/Modbus仿真环境搭建.md
读写:https://github.com/simonvetter/modbus

有关golang + modbus tcp的更多相关文章

  1. 华为OD机试Golang解题 - 任务总执行时长 - 2

    华为Od必看系列华为OD机试全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理已参加机试人员的实战技巧华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典文章目录华为Od必看系列使用说明本期题目:任务总执行时长题目输入输出示例一输入输出说明go代码实现华为OD其它语言版本

  2. 【编程实践】Golang 获取HTTP请求的IP地址 - 2

    目录Golang获取HTTP请求的IP地址HTTP的发展历史3,HTTP所在的网络层次4,HTTP请求与响应

  3. Golang SQL 查询语法 - 2

    关闭。这个问题需要debuggingdetails.它目前不接受答案。编辑问题以包含desiredbehavior,aspecificproblemorerror,andtheshortestcodenecessarytoreproducetheproblem.这将有助于其他人回答问题。关闭4年前。Improvethisquestion在golang代码中使用sql查询获取语法错误。golang中此SQL查询所需的正确语法:rows,errQuery:=dbCon.Query("SELECTB.LatestDate,A.SVRNameASServerName,A.DRIVE,A.Tot

  4. go - 在golang中的表达式中检查括号是否平衡[保持] - 2

    给定表达式字符串exp,编写程序检查exp中“{”、“}”、“(”、“)”、“[”、“]的对和顺序是否正确。packagemainimport("fmt"stack"github.com/golang-collections/collections/stack")funcmain(){s:="(a[0]+b[2c[6]])){24+53}"stackO:=stack.New()stackmap:=map[string]string{"[":"]","(":")","{":"}"}varstr=""for_,num:=ranges{str=string(num)if(str=="{"||

  5. Golang libphonenumber - 2

    关闭。这个问题是notreproducibleorwascausedbytypos.它目前不接受答案。这个问题是由于错别字或无法再重现的问题引起的。虽然类似的问题可能是on-topic在这里,这个问题的解决方式不太可能帮助future的读者。关闭3年前。Improvethisquestion如何通过使用此库在golang中传递数字来获取国家代码:https://godoc.org/github.com/nyaruka/phonenumbers?

  6. go - func 的语法 Golang 错误 - 2

    为什么下面的代码会抛出意外的函数错误?我看到错误./func_correct.go:4:syntaxerror:unexpectedfunc,expectingnamepackagemainfunc(st*Stack)Pop()int{v:=0forix:=len(st)-1;ix>=0;ix--{ifv=st[ix];v!=0{st[ix]=0returnv}}return0}funcmain(){Pop()} 最佳答案 定义堆栈类型在main中为其创建一个变量对其调用Pop代码:packagemainimport"fmt"typ

  7. go - 进程花费了太长时间程序退出:Golang错误 - 2

    Thisquestionalreadyhasanswershere:ForloopoftwovariablesinGo(3个答案)2年前关闭。我正在通过Gotour在Go中使用for循环我跑的时候packagemainimport"fmt"funcmain(){sum:=1forsum程序运行正常,输出为1024但是当我更改sum:=0时packagemainimport"fmt"funcmain(){sum:=0forsum它给出了错误的说法processtooktoolongProgramexited.编辑:我沉迷于Go巡回赛,以至于我无法意识到,我犯了一个逻辑错误:P。

  8. json - 如何在golang中将结构解码到 map 中 - 2

    关闭。这个问题需要debuggingdetails.它目前不接受答案。编辑问题以包含desiredbehavior,aspecificproblemorerror,andtheshortestcodenecessarytoreproducetheproblem.这将有助于其他人回答问题。关闭4年前。Improvethisquestion我有一个Json:{"id":"me","name":"myname","planets":{"EARTH":3,"MARS":4}}我不知道如何将planets字段解码为map[string]int,所以我将访问元素而无需解码它们,就像在这个例子中一样

  9. mysql - golang MySQL "connection refused" - 2

    我是Go(Golang)的新手。我写了一个简单的基准程序来测试MySQL的并发处理。当我增加并发channel数时,不断收到“dialtcp52.55.254.165:3306:getsockopt:connectionrefused”、“unexpectedEOF”错误。每个go例程都将1到n行批量插入到一个简单的客户表中。该程序允许设置可变插入大小(单个语句中的行数)和并行go例程的数量(每个go例程执行上面的一个插入)。程序在小数字row寻找线索。基于它们,我设置了数据库最大连接数以及“max_allowed_pa​​cket”和“max_connections”。我还设置了go

  10. go - Golang "func (t *SomeType) myFuncName(param1, param2)"语法是什么意思 - 2

    这个问题在这里已经有了答案:Functiondeclarationsyntax:thingsinparenthesisbeforefunctionname(3个答案)WhatsthedifferenceoffunctionsandmethodsinGo?(5个答案)ParameterbeforethefunctionnameinGo?[duplicate](1个回答)WhatdothebracketsafterfuncmeaninGo?[duplicate](1个回答)关闭8个月前。我正在学习Golang-在教程中我经常看到这样的语法:typeSomeTypestruct{//stru

随机推荐