| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135 |
- package main
- import (
- "context"
- "encoding/binary"
- "encoding/hex"
- "errors"
- "fmt"
- "runtime/debug"
- "time"
- "github.com/sirupsen/logrus"
- "github.com/thinkgos/timing/v3"
- "lc/common/mqtt"
- "lc/common/protocol"
- "lc/common/util"
- "lc/edge/ipole/modbus"
- )
- var ModbusRtuProtocol = "ModbusRTU"
- var ErrInvalidFunccode = errors.New("invalid function code")
- const (
- rtuExceptionSize = 5
- DefaultReadyQueuesLength = 256
- )
- type Request struct {
- Rtuinfo *protocol.DevInfo //RTU信息
- CID uint8 //cid->采集命令,下发给设备的命令
- FuncCode byte // 功能码
- Address uint16 // 请求数据用实际地址
- Quantity uint16 // 请求数量
- ScanRate time.Duration // 扫描速率scan rate
- TxCnt uint64 // 发送计数
- ErrCnt uint64 // 发送错误计数
- tmHandler func()
- }
- type ModbusRtu struct {
- reqList []*Request
- devinfo *protocol.DevInfo
- model *protocol.IotModel
- ready chan *Request
- ctx context.Context
- cancel context.CancelFunc
- chanDevInfo chan *protocol.DevInfo //设备管理更新
- chanModelInfo chan *ModelInfo //设备管理更新
- }
- func NewModbusRtu(info *protocol.DevInfo) Device {
- ctx, cancel := context.WithCancel(context.Background())
- rtu := &ModbusRtu{
- devinfo: info,
- ready: make(chan *Request, DefaultReadyQueuesLength),
- ctx: ctx,
- cancel: cancel,
- chanDevInfo: make(chan *protocol.DevInfo),
- chanModelInfo: make(chan *ModelInfo),
- }
- iot, err := loadModel(info.TID)
- if err != nil {
- logrus.Errorf("ReloadModel:加载模型[tid=%d]文件发生错误:%s", info.TID, err.Error())
- } else {
- if iot.Protocol == ModbusRtuProtocol {
- rtu.model = iot
- } else {
- logrus.Error("ModbusRtu.UpdateModel:物模型错误,非ModbusRTU协议")
- }
- }
- mapRtuUploadManager.Store(info.DevCode, NewRtuUploadManager(info))
- return rtu
- }
- func (o *ModbusRtu) Start() {
- GetMQTTMgr().Subscribe(GetTopic(o.GetDevType(), o.devinfo.DevCode, protocol.TP_MODBUS_CONTROL), mqtt.ExactlyOnce, o.HandleTpWControl, ToAll)
- go o.HandleData()
- }
- func (o *ModbusRtu) Stop() {
- //停止采集和处理
- o.cancel()
- //停止上传
- if rtumgr, ok := mapRtuUploadManager.Load(o.devinfo.DevCode); ok {
- prtumgr := rtumgr.(*RtuUploadManager)
- if prtumgr != nil {
- prtumgr.Stop()
- }
- }
- }
- func (o *ModbusRtu) UpdateInfo(devinfo protocol.DevInfo) {
- o.chanDevInfo <- &devinfo
- }
- func (o *ModbusRtu) GetDevInfo() *protocol.DevInfo {
- return o.devinfo
- }
- func (o *ModbusRtu) UpdateModel(tid uint16, flag int) {
- if tid > 0 {
- mi := ModelInfo{
- TID: tid,
- Flag: flag,
- }
- o.chanModelInfo <- &mi
- }
- }
- func (o *ModbusRtu) UpdateModel2(mi *ModelInfo) {
- if o.devinfo.TID != mi.TID {
- return
- }
- if mi.Flag == 0 {
- logrus.Errorf("设备[%s]的物模型[tid=%d]模型文件被删除,下次启动即将生效。", o.devinfo.DevCode, mi.TID)
- return
- }
- logrus.Debugf("ModbusRtu.UpdateModel2:更新设备[%s]的物模型[%d]", o.devinfo.DevCode, mi.TID)
- iot, err := loadModel(mi.TID)
- if err != nil {
- logrus.Errorf("ModbusRtu.UpdateModel2:加载模型[%d]文件错误:%s", mi.TID, err.Error())
- return
- }
- if iot.Protocol == ModbusRtuProtocol { //合法的物模型
- o.model = iot
- o.clearRequest()
- o.updateRequest()
- logrus.Infof("ModbusRtu.UpdateModel2:更新设备[%s]的物模型[%d]成功", o.devinfo.DevCode, mi.TID)
- } else {
- logrus.Error("ModbusRtu.UpdateModel2:物模型错误,TID和文件名tid不一致或协议非ModbusRTU协议")
- }
- }
- func (o *ModbusRtu) GetDevType() string {
- if o.devinfo.DevType == 1 {
- return protocol.DT_CONCENTRATOR
- } else if o.devinfo.DevType == 2 {
- return protocol.DT_ENVIRONMENT
- } else if o.devinfo.DevType == 4 {
- return protocol.DT_LIQUID
- } else if o.devinfo.DevType == 5 {
- return protocol.DT_ROAD_COND
- }
- return "unknown"
- }
- // HandleData 数据处理协程
- func (o *ModbusRtu) HandleData() {
- defer func() {
- if err := recover(); err != nil {
- logrus.Error("ModbusRtu.HandleData:panic:", err)
- logrus.Error("stack:", string(debug.Stack()))
- go o.HandleData()
- }
- }()
- o.updateRequest()
- var req *Request
- for {
- select {
- case <-o.ctx.Done():
- logrus.Errorf("设备[%s]的HandleData退出,原因:%v", o.devinfo.DevCode, o.ctx.Err())
- return
- case devinfo_ := <-o.chanDevInfo:
- o.devinfo = devinfo_
- case mi := <-o.chanModelInfo:
- o.UpdateModel2(mi) //物模型配置文件更新
- case req = <-o.ready: //查看是否有准备好的请求
- o.procRequest(req)
- default:
- time.Sleep(time.Millisecond * 100)
- }
- }
- }
- func (o *ModbusRtu) clearRequest() {
- for _, v := range o.reqList {
- v.ScanRate = 0 //置为0,则不再执行新请求
- }
- o.reqList = nil
- }
- func (o *ModbusRtu) updateRequest() {
- for k, v := range o.model.Packet {
- r := Request{CID: k, Rtuinfo: o.devinfo, FuncCode: v.Code, Address: v.Addr,
- Quantity: v.Quantity, ScanRate: time.Duration(v.Cycle) * time.Millisecond,
- }
- if err := o.AddGatherJob(&r); err != nil {
- logrus.Errorf("给串口[%d]添加采集任务[DevCode=%s,SlaveID=%d,TID=%d,CID=%d]失败:%s",
- o.devinfo.Code, o.devinfo.DevCode, o.devinfo.DevID, o.devinfo.TID, k, err.Error())
- } else {
- o.reqList = append(o.reqList, &r)
- }
- }
- }
- // AddGatherJob 增加采集任务
- func (o *ModbusRtu) AddGatherJob(r *Request) error {
- if err := o.ctx.Err(); err != nil {
- return err
- }
- if r.Rtuinfo.DevID < modbus.AddressMin || r.Rtuinfo.DevID > modbus.AddressMax {
- return fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- r.Rtuinfo.DevID, modbus.AddressMin, modbus.AddressMax)
- }
- if r.FuncCode == modbus.FuncCodeReadCoils || r.FuncCode == modbus.FuncCodeReadDiscreteInputs ||
- r.FuncCode == modbus.FuncCodeReadInputRegisters || r.FuncCode == modbus.FuncCodeReadHoldingRegisters {
- r.tmHandler = func() {
- select {
- case <-o.ctx.Done():
- return
- case o.ready <- r:
- default:
- timing.AddJobFunc(r.tmHandler, r.ScanRate*time.Millisecond)
- }
- }
- timing.AddJobFunc(r.tmHandler, r.ScanRate)
- } else {
- return ErrInvalidFunccode
- }
- return nil
- }
- func (o *ModbusRtu) ProcReadCoils(cid uint8, address, quality uint16, valBuf []byte) {
- }
- func (o *ModbusRtu) ProcReadDiscretes(cid uint8, address, quality uint16, valBuf []byte) {
- }
- func (o *ModbusRtu) ProcReadHoldingRegisters(cid uint8, address, quality uint16, valBuf []byte) {
- logrus.Debugf("收到自寄存器地址%d;开始的数据:%v", address, hex.EncodeToString(valBuf))
- dataLen := len(valBuf)
- if o.model.Packet[cid].Resplen != uint(dataLen) {
- return
- }
- dataMap := make(map[uint16]float64)
- for _, v := range o.model.DataUp {
- if v.Cid != cid || v.Len == 0 {
- continue
- }
- if int(v.Start+v.Len) > dataLen { //索引超长,忽略该项目
- logrus.Errorf("物模型[TID=%d]配置的数据项[sid=%d]start加len超过采集响应的长度", o.model.TID, v.SID)
- continue
- }
- var fVal float64
- strVal := valBuf[v.Start : v.Start+v.Len]
- if v.Len == 2 {
- var u16 uint16
- if v.Endian == 0 { //大端
- u16 = binary.BigEndian.Uint16(strVal)
- } else { //小端
- u16 = binary.LittleEndian.Uint16(strVal)
- }
- if v.Type == 0 { //处理符号
- fVal = float64(u16)
- } else {
- fVal = float64(int16(u16))
- }
- } else if v.Len == 4 {
- if v.Endian == 0 {
- fVal = float64(util.BEByteToFloat32(strVal))
- } else {
- fVal = float64(util.LEByteToFloat32(strVal))
- }
- } else if v.Len == 1 {
- if v.Type == 0 { //处理符号
- fVal = float64(strVal[0])
- } else {
- fVal = float64(int8(strVal[0]))
- }
- }
- if v.Ratio > 0 {
- fVal = fVal / float64(v.Ratio)
- }
- fVal = fVal - float64(v.Base)
- logrus.Debugf(v.NameZh, ":", fVal)
- if v.Type == 0 || v.Type == 1 { //整数
- dataMap[v.SID] = Precision(fVal, 0, true)
- } else if v.Type == 2 || v.Type == 3 { //浮点数
- dataMap[v.SID] = Precision(fVal, 3, false)
- }
- }
- if rtumgr, ok := mapRtuUploadManager.Load(o.devinfo.DevCode); ok {
- prtumgr := rtumgr.(*RtuUploadManager)
- if prtumgr != nil {
- prtumgr.AddData(dataMap)
- }
- }
- }
- func (o *ModbusRtu) ProcReadInputRegisters(cid uint8, address, quality uint16, valBuf []byte) {
- logrus.Debugf("收到自寄存器地址%d, 开始的数据:%v, 长度为:%d", address, hex.EncodeToString(valBuf), len(valBuf))
- dataLen := len(valBuf)
- if o.model.Packet[cid].Resplen != uint(dataLen) {
- logrus.Errorf("ProcReadInputRegisters len no equal")
- return
- }
- dataMap := make(map[uint16]float64)
- for _, v := range o.model.DataUp {
- if v.Cid != cid || v.Len == 0 {
- logrus.Errorf("ProcReadInputRegisters Cid no equal")
- continue
- }
- if int(v.Start+v.Len) > dataLen { //索引超长,忽略该项目
- logrus.Errorf("物模型[TID=%d]配置的数据项[sid=%d]start加len超过采集响应的长度", o.model.TID, v.SID)
- continue
- }
- var fVal float64
- strVal := valBuf[v.Start : v.Start+v.Len]
- if v.Len == 2 {
- var u16 uint16
- if v.Endian == 0 { //大端
- u16 = binary.BigEndian.Uint16(strVal)
- } else { //小端
- u16 = binary.LittleEndian.Uint16(strVal)
- }
- if v.Type == 0 { //处理符号
- fVal = float64(u16)
- } else {
- fVal = float64(int16(u16))
- }
- } else if v.Len == 4 {
- if v.Endian == 0 {
- fVal = float64(util.BEByteToFloat32(strVal))
- } else {
- fVal = float64(util.LEByteToFloat32(strVal))
- }
- } else if v.Len == 1 {
- if v.Type == 0 { //处理符号
- fVal = float64(strVal[0])
- } else {
- fVal = float64(int8(strVal[0]))
- }
- }
- if v.Ratio > 0 {
- fVal = fVal / float64(v.Ratio)
- }
- fVal = fVal - float64(v.Base)
- if v.Type == 0 || v.Type == 1 { //整数
- dataMap[v.SID] = Precision(fVal, 0, true)
- } else if v.Type == 2 || v.Type == 3 { //浮点数
- dataMap[v.SID] = Precision(fVal, 3, false)
- }
- }
- logrus.Debugf("ProcReadInputRegisters dataMap = %v", dataMap)
- if rtumgr, ok := mapRtuUploadManager.Load(o.devinfo.DevCode); ok {
- prtumgr := rtumgr.(*RtuUploadManager)
- if prtumgr != nil {
- prtumgr.AddData(dataMap)
- }
- }
- }
- func (o *ModbusRtu) ProcResult(err error, req *Request) {
- if err == nil {
- if req.ErrCnt > 0 {
- req.ErrCnt = 0
- }
- } else {
- //连续采集超过5次都错误,则报告设备离线
- if req.ErrCnt == 5 {
- logrus.Errorf("采集设备[DevCode=%s,SlaveID=%d,Tid=%d,Cid=%d]的数据发生错误:%s",
- req.Rtuinfo.DevCode, req.Rtuinfo.DevID, req.Rtuinfo.TID, req.CID, err.Error())
- //离线状态报告
- var obj protocol.Pack_UploadData
- if str, err := obj.EnCode(req.Rtuinfo.DevCode, appConfig.GID, GetNextUint64(), err, req.Rtuinfo.TID, nil); err == nil {
- topic := GetTopic(o.GetDevType(), req.Rtuinfo.DevCode, protocol.TP_MODBUS_DATA)
- GetMQTTMgr().Publish(topic, str, 0, ToAll)
- logrus.Debugf("topic:%s,payload:%s", topic, str)
- }
- }
- }
- }
- func (o *ModbusRtu) procRequest(req *Request) {
- var err error
- var result []byte
- defer func() {
- if err := recover(); err != nil {
- logrus.Error("procRequest:panic:", err)
- logrus.Error("stack:", string(debug.Stack()))
- }
- }()
- req.TxCnt++
- switch req.FuncCode {
- // A bit of access read
- case modbus.FuncCodeReadCoils:
- result, err = o.ReadCoils(req.Rtuinfo.DevID, req.Address, req.Quantity)
- logrus.Debugf("ReadCoils result:= %s", string(result))
- if err == nil {
- o.ProcReadCoils(req.CID, req.Address, req.Quantity, result)
- }
- case modbus.FuncCodeReadDiscreteInputs:
- result, err = o.ReadDiscreteInputs(req.Rtuinfo.DevID, req.Address, req.Quantity)
- logrus.Debugf("ReadDiscreteInputs result:= %s", string(result))
- if err == nil {
- o.ProcReadDiscretes(req.CID, req.Address, req.Quantity, result)
- }
- // 16-bit access read
- case modbus.FuncCodeReadHoldingRegisters: //03
- result, err = o.ReadHoldingRegistersBytes(req.Rtuinfo.DevID, req.Address, req.Quantity)
- logrus.Debugf("ReadHoldingRegistersBytes result:= %s", string(result))
- if err == nil {
- o.ProcReadHoldingRegisters(req.CID, req.Address, req.Quantity, result)
- } else {
- logrus.Errorf("设备上传网关数据为空!可能原因:未配置正确,请检查配置文件;设备线路或设备本身损坏;网关串口损坏.\n")
- logrus.Errorf("error:%v\n", err)
- }
- case modbus.FuncCodeReadInputRegisters:
- result, err = o.ReadInputRegistersBytes(req.Rtuinfo.DevID, req.Address, req.Quantity)
- logrus.Debugf("ReadInputRegistersBytes result:= %s", string(result))
- if err == nil {
- o.ProcReadInputRegisters(req.CID, req.Address, req.Quantity, result)
- }
- }
- if err != nil {
- req.ErrCnt++
- }
- o.ProcResult(err, req)
- if req.ScanRate > 0 {
- timing.AddJobFunc(req.tmHandler, req.ScanRate)
- }
- }
- func (o *ModbusRtu) HandleTpWControl(m mqtt.Message) {
- var obj protocol.Pack_ControlData
- var ret protocol.Pack_Ack
- var err error
- if err = obj.DeCode(m.PayloadString()); err == nil {
- if v, ok := o.model.DataDown[obj.Data.Sid]; ok {
- var data []byte
- if v.Vallen == 1 { //1个字节
- data = make([]byte, 1, 1)
- data = append(data, byte(obj.Data.Val))
- } else if v.Vallen == 2 { //2个字节
- data = make([]byte, 2, 2)
- binary.BigEndian.PutUint16(data, uint16(obj.Data.Val))
- } else if v.Vallen == 4 {
- data = make([]byte, 4, 4)
- binary.BigEndian.PutUint32(data, uint32(obj.Data.Val))
- }
- err = o.WriteData(o.devinfo.DevID, v.Code, v.Addr, v.Quantity, data)
- } else {
- err = errors.New(fmt.Sprintf("物模型[TID=%d]未配置该SID[sid=%d],请确保模型文件存在", obj.Data.Tid, obj.Data.Sid))
- }
- }
- if str, err := ret.EnCode(o.devinfo.DevCode, appConfig.GID, obj.Seq, err); err == nil {
- GetMQTTMgr().Publish(GetTopic(o.GetDevType(), o.devinfo.DevCode, protocol.TP_MODBUS_CONTROL_ACK), str, 0, ToAll)
- }
- }
- func (o *ModbusRtu) WriteData(slaveID, funcCode byte, address, quantity uint16, value []byte) error {
- var err error
- defer func() {
- if err := recover(); err != nil {
- logrus.Error("WriteData:panic:", err)
- logrus.Error("stack:", string(debug.Stack()))
- }
- }()
- switch funcCode {
- case modbus.FuncCodeWriteSingleCoil: //5
- var isOn = false
- if len(value) != 2 {
- return errors.New("数据长度不对")
- }
- if binary.BigEndian.Uint16(value) > 0 {
- isOn = true
- }
- err = o.WriteSingleCoil(slaveID, address, isOn)
- case modbus.FuncCodeWriteMultipleCoils: //15
- err = o.WriteMultipleCoils(slaveID, address, quantity, value)
- case modbus.FuncCodeWriteSingleRegister: //6
- err = o.WriteSingleRegister(slaveID, address, binary.BigEndian.Uint16(value))
- case modbus.FuncCodeWriteMultipleRegisters: //16
- err = o.WriteMultipleRegistersBytes(slaveID, address, quantity, value)
- default:
- logrus.Errorf("不支持的功能码:%d", funcCode)
- err = errors.New("不支持的功能码")
- }
- return err
- }
- func (o *ModbusRtu) SendRecvData(aduRequest []byte) (aduResponse []byte, err error) {
- serial := GetSerialMgr().GetSerialPort(o.devinfo.Code)
- if serial == nil {
- return nil, ErrClosedConnection
- }
- return serial.SendRecvData(aduRequest, FlagModbusRtu, o.devinfo.WaitTime)
- }
- // Send request to the remote server, it implements on SendRawFrame
- func (o *ModbusRtu) Send(slaveID byte, request modbus.ProtocolDataUnit) (modbus.ProtocolDataUnit, error) {
- var response modbus.ProtocolDataUnit
- aduRequest, err := modbus.EncodeRTUFrame(slaveID, request)
- if err != nil {
- return response, err
- }
- logrus.Debugf("发送给设备: %s 的数据: [% x]", o.devinfo.DevCode, aduRequest)
- aduResponse, err := o.SendRecvData(aduRequest)
- logrus.Debugf("收到的数据: %s 的数据: [% x]", o.devinfo.DevCode, aduResponse)
- if err != nil {
- logrus.Debugf("ReadHoldingRegistersBytes SendRecvData err:= %v", err)
- return response, err
- }
- rspSlaveID, pdu, err := modbus.DecodeRTUFrame(aduResponse)
- if err != nil {
- logrus.Debugf("ReadHoldingRegistersBytes DecodeRTUFrame err:= %v", err)
- return response, err
- }
- response = modbus.ProtocolDataUnit{FuncCode: pdu[0], Data: pdu[1:]}
- return response, modbus.Verify(slaveID, rspSlaveID, request, response)
- }
- // SendPdu send pdu request to the remote server
- func (o *ModbusRtu) SendPdu(slaveID byte, pduRequest []byte) ([]byte, error) {
- if len(pduRequest) < modbus.PduMinSize || len(pduRequest) > modbus.PduMaxSize {
- return nil, fmt.Errorf("modbus: pdu size '%v' must not be between '%v' and '%v'", len(pduRequest), modbus.PduMinSize, modbus.PduMaxSize)
- }
- request := modbus.ProtocolDataUnit{FuncCode: pduRequest[0], Data: pduRequest[1:]}
- requestAdu, err := modbus.EncodeRTUFrame(slaveID, request)
- if err != nil {
- return nil, err
- }
- aduResponse, err := o.SendRecvData(requestAdu)
- if err != nil {
- return nil, err
- }
- rspSlaveID, pdu, err := modbus.DecodeRTUFrame(aduResponse)
- if err != nil {
- return nil, err
- }
- // PDU pass slaveID & crc
- return pdu, modbus.Verify(slaveID, rspSlaveID, request, modbus.ProtocolDataUnit{FuncCode: pdu[0], Data: pdu[1:]})
- }
- // ReadCoils Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x01)
- // Starting address : 2 bytes
- // Quantity of coils : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x01)
- // Byte count : 1 byte
- // Coil status : N* bytes (=N or N+1)
- // return coils status
- func (o *ModbusRtu) ReadCoils(slaveID byte, address, quantity uint16) ([]byte, error) {
- if slaveID < modbus.AddressMin || slaveID > modbus.AddressMax {
- return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressMin, modbus.AddressMax)
- }
- if quantity < modbus.ReadBitsQuantityMin || quantity > modbus.ReadBitsQuantityMax {
- return nil, fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
- quantity, modbus.ReadBitsQuantityMin, modbus.ReadBitsQuantityMax)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{FuncCode: modbus.FuncCodeReadCoils, Data: uint162Bytes(address, quantity)})
- switch {
- case err != nil:
- return nil, err
- case len(response.Data)-1 != int(response.Data[0]):
- return nil, fmt.Errorf("modbus: response byte size '%v' does not match count '%v'",
- len(response.Data)-1, int(response.Data[0]))
- case uint16(response.Data[0]) != (quantity+7)/8:
- return nil, fmt.Errorf("modbus: response byte size '%v' does not match quantity to bytes '%v'",
- response.Data[0], (quantity+7)/8)
- }
- return response.Data[1:], nil
- }
- // ReadDiscreteInputs Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x02)
- // Starting address : 2 bytes
- // Quantity of inputs : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x02)
- // Byte count : 1 byte
- // Input status : N* bytes (=N or N+1)
- // return result data
- func (o *ModbusRtu) ReadDiscreteInputs(slaveID byte, address, quantity uint16) ([]byte, error) {
- if slaveID < modbus.AddressMin || slaveID > modbus.AddressMax {
- return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressMin, modbus.AddressMax)
- }
- if quantity < modbus.ReadBitsQuantityMin || quantity > modbus.ReadBitsQuantityMax {
- return nil, fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
- quantity, modbus.ReadBitsQuantityMin, modbus.ReadBitsQuantityMax)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeReadDiscreteInputs,
- Data: uint162Bytes(address, quantity),
- })
- switch {
- case err != nil:
- return nil, err
- case len(response.Data)-1 != int(response.Data[0]):
- return nil, fmt.Errorf("modbus: response byte size '%v' does not match count '%v'",
- len(response.Data)-1, response.Data[0])
- case uint16(response.Data[0]) != (quantity+7)/8:
- return nil, fmt.Errorf("modbus: response byte size '%v' does not match quantity to bytes '%v'",
- response.Data[0], (quantity+7)/8)
- }
- return response.Data[1:], nil
- }
- // WriteSingleCoil Request:
- //
- // Slave Id : 1 byte
- // Function code : 1 byte (0x05)
- // Output address : 2 bytes
- // Output value : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x05)
- // Output address : 2 bytes
- // Output value : 2 bytes
- func (o *ModbusRtu) WriteSingleCoil(slaveID byte, address uint16, isOn bool) error {
- if slaveID > modbus.AddressMax {
- return fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressBroadCast, modbus.AddressMax)
- }
- var value uint16
- if isOn { // The requested ON/OFF state can only be 0xFF00 and 0x0000
- value = 0xFF00
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeWriteSingleCoil,
- Data: uint162Bytes(address, value),
- })
- switch {
- case err != nil:
- return err
- case len(response.Data) != 4:
- // Fixed response length
- return fmt.Errorf("modbus: response data size '%v' does not match expected '%v'",
- len(response.Data), 4)
- case binary.BigEndian.Uint16(response.Data) != address:
- // check address
- return fmt.Errorf("modbus: response address '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data), address)
- case binary.BigEndian.Uint16(response.Data[2:]) != value:
- // check value
- return fmt.Errorf("modbus: response value '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data[2:]), value)
- }
- return nil
- }
- // WriteMultipleCoils Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x0F)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- // Byte count : 1 byte
- // Outputs value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x0F)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- func (o *ModbusRtu) WriteMultipleCoils(slaveID byte, address, quantity uint16, value []byte) error {
- if slaveID > modbus.AddressMax {
- return fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressBroadCast, modbus.AddressMax)
- }
- if quantity < modbus.WriteBitsQuantityMin || quantity > modbus.WriteBitsQuantityMax {
- return fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
- quantity, modbus.WriteBitsQuantityMin, modbus.WriteBitsQuantityMax)
- }
- if len(value)*8 < int(quantity) {
- return fmt.Errorf("modbus: value bits size '%v' does not greater or equal to quantity '%v'", len(value)*8, quantity)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeWriteMultipleCoils,
- Data: pduDataBlockSuffix(value, address, quantity),
- })
- switch {
- case err != nil:
- return err
- case len(response.Data) != 4:
- // Fixed response length
- return fmt.Errorf("modbus: response data size '%v' does not match expected '%v'",
- len(response.Data), 4)
- case binary.BigEndian.Uint16(response.Data) != address:
- return fmt.Errorf("modbus: response address '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data), address)
- case binary.BigEndian.Uint16(response.Data[2:]) != quantity:
- return fmt.Errorf("modbus: response quantity '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data[2:]), quantity)
- }
- return nil
- }
- /*********************************16-bits**************************************/
- // ReadInputRegistersBytes Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x04)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x04)
- // Byte count : 1 byte
- // Input registers : Nx2 bytes
- func (o *ModbusRtu) ReadInputRegistersBytes(slaveID byte, address, quantity uint16) ([]byte, error) {
- if slaveID < modbus.AddressMin || slaveID > modbus.AddressMax {
- return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressMin, modbus.AddressMax)
- }
- if quantity < modbus.ReadRegQuantityMin || quantity > modbus.ReadRegQuantityMax {
- return nil, fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
- quantity, modbus.ReadRegQuantityMin, modbus.ReadRegQuantityMax)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeReadInputRegisters,
- Data: uint162Bytes(address, quantity),
- })
- switch {
- case err != nil:
- return nil, err
- case len(response.Data)-1 != int(response.Data[0]):
- return nil, fmt.Errorf("modbus: response data size '%v' does not match count '%v'",
- len(response.Data)-1, response.Data[0])
- case uint16(response.Data[0]) != quantity*2:
- return nil, fmt.Errorf("modbus: response data size '%v' does not match quantity to bytes '%v'",
- response.Data[0], quantity*2)
- }
- return response.Data[1:], nil
- }
- // ReadInputRegisters Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x04)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x04)
- // Byte count : 1 byte
- // Input registers : N 2-bytes
- func (o *ModbusRtu) ReadInputRegisters(slaveID byte, address, quantity uint16) ([]uint16, error) {
- b, err := o.ReadInputRegistersBytes(slaveID, address, quantity)
- if err != nil {
- return nil, err
- }
- return bytes2Uint16(b), nil
- }
- // ReadHoldingRegistersBytes Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x03)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x03)
- // Byte count : 1 byte
- // Register value : Nx2 bytes
- func (o *ModbusRtu) ReadHoldingRegistersBytes(slaveID byte, address, quantity uint16) ([]byte, error) {
- if slaveID < modbus.AddressMin || slaveID > modbus.AddressMax {
- return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressMin, modbus.AddressMax)
- }
- if quantity < modbus.ReadRegQuantityMin || quantity > modbus.ReadRegQuantityMax {
- return nil, fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
- quantity, modbus.ReadRegQuantityMin, modbus.ReadRegQuantityMax)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeReadHoldingRegisters,
- Data: uint162Bytes(address, quantity),
- })
- switch {
- case err != nil:
- return nil, err
- case len(response.Data)-1 != int(response.Data[0]):
- return nil, fmt.Errorf("modbus: response data size '%v' does not match count '%v'",
- len(response.Data)-1, response.Data[0])
- //case uint16(response.Data[0]) != quantity*2:
- // return nil, fmt.Errorf("modbus: response data size '%v' does not match quantity to bytes '%v'",
- // response.Data[0], quantity*2)
- }
- return response.Data[1:], nil
- }
- // ReadHoldingRegisters Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x03)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x03)
- // Byte count : 1 byte
- // Register value : N 2-bytes
- func (o *ModbusRtu) ReadHoldingRegisters(slaveID byte, address, quantity uint16) ([]uint16, error) {
- b, err := o.ReadHoldingRegistersBytes(slaveID, address, quantity)
- if err != nil {
- return nil, err
- }
- return bytes2Uint16(b), nil
- }
- // WriteSingleRegister Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x06)
- // Register address : 2 bytes
- // Register value : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x06)
- // Register address : 2 bytes
- // Register value : 2 bytes
- func (o *ModbusRtu) WriteSingleRegister(slaveID byte, address, value uint16) error {
- if slaveID > modbus.AddressMax {
- return fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressBroadCast, modbus.AddressMax)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeWriteSingleRegister,
- Data: uint162Bytes(address, value),
- })
- switch {
- case err != nil:
- return err
- case len(response.Data) != 4:
- // Fixed response length
- return fmt.Errorf("modbus: response data size '%v' does not match expected '%v'",
- len(response.Data), 4)
- case binary.BigEndian.Uint16(response.Data) != address:
- return fmt.Errorf("modbus: response address '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data), address)
- case binary.BigEndian.Uint16(response.Data[2:]) != value:
- return fmt.Errorf("modbus: response value '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data[2:]), value)
- }
- return nil
- }
- // WriteMultipleRegistersBytes Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- // Byte count : 1 byte
- // Registers value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- func (o *ModbusRtu) WriteMultipleRegistersBytes(slaveID byte, address, quantity uint16, value []byte) error {
- if slaveID > modbus.AddressMax {
- return fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressBroadCast, modbus.AddressMax)
- }
- if quantity < modbus.WriteRegQuantityMin || quantity > modbus.WriteRegQuantityMax {
- return fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
- quantity, modbus.WriteRegQuantityMin, modbus.WriteRegQuantityMax)
- }
- if len(value) != int(quantity*2) {
- return fmt.Errorf("modbus: value length '%v' does not twice as quantity '%v'", len(value), quantity)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeWriteMultipleRegisters,
- Data: pduDataBlockSuffix(value, address, quantity),
- })
- switch {
- case err != nil:
- return err
- case len(response.Data) != 4:
- // Fixed response length
- return fmt.Errorf("modbus: response data size '%v' does not match expected '%v'",
- len(response.Data), 4)
- case binary.BigEndian.Uint16(response.Data) != address:
- return fmt.Errorf("modbus: response address '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data), address)
- case binary.BigEndian.Uint16(response.Data[2:]) != quantity:
- return fmt.Errorf("modbus: response quantity '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data[2:]), quantity)
- }
- return nil
- }
- // WriteMultipleRegisters Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- // Byte count : 1 byte
- // Registers value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- func (o *ModbusRtu) WriteMultipleRegisters(slaveID byte, address, quantity uint16, value []uint16) error {
- return o.WriteMultipleRegistersBytes(slaveID, address, quantity, uint162Bytes(value...))
- }
- // MaskWriteRegister Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x16)
- // Reference address : 2 bytes
- // AND-mask : 2 bytes
- // OR-mask : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x16)
- // Reference address : 2 bytes
- // AND-mask : 2 bytes
- // OR-mask : 2 bytes
- func (o *ModbusRtu) MaskWriteRegister(slaveID byte, address, andMask, orMask uint16) error {
- if slaveID > modbus.AddressMax {
- return fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressBroadCast, modbus.AddressMax)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeMaskWriteRegister,
- Data: uint162Bytes(address, andMask, orMask),
- })
- switch {
- case err != nil:
- return err
- case len(response.Data) != 6:
- // Fixed response length
- return fmt.Errorf("modbus: response data size '%v' does not match expected '%v'",
- len(response.Data), 6)
- case binary.BigEndian.Uint16(response.Data) != address:
- return fmt.Errorf("modbus: response address '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data), address)
- case binary.BigEndian.Uint16(response.Data[2:]) != andMask:
- return fmt.Errorf("modbus: response AND-mask '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data[2:]), andMask)
- case binary.BigEndian.Uint16(response.Data[4:]) != orMask:
- return fmt.Errorf("modbus: response OR-mask '%v' does not match request '%v'",
- binary.BigEndian.Uint16(response.Data[4:]), orMask)
- }
- return nil
- }
- // ReadWriteMultipleRegistersBytes Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x17)
- // Read starting address : 2 bytes
- // Quantity to read : 2 bytes
- // Write starting address: 2 bytes
- // Quantity to write : 2 bytes
- // Write byte count : 1 byte
- // Write registers value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x17)
- // Byte count : 1 byte
- // Read registers value : Nx2 bytes
- func (o *ModbusRtu) ReadWriteMultipleRegistersBytes(slaveID byte, readAddress, readQuantity,
- writeAddress, writeQuantity uint16, value []byte) ([]byte, error) {
- if slaveID < modbus.AddressMin || slaveID > modbus.AddressMax {
- return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressMin, modbus.AddressMax)
- }
- if readQuantity < modbus.ReadWriteOnReadRegQuantityMin || readQuantity > modbus.ReadWriteOnReadRegQuantityMax {
- return nil, fmt.Errorf("modbus: quantity to read '%v' must be between '%v' and '%v'",
- readQuantity, modbus.ReadWriteOnReadRegQuantityMin, modbus.ReadWriteOnReadRegQuantityMax)
- }
- if writeQuantity < modbus.ReadWriteOnWriteRegQuantityMin || writeQuantity > modbus.ReadWriteOnWriteRegQuantityMax {
- return nil, fmt.Errorf("modbus: quantity to write '%v' must be between '%v' and '%v'",
- writeQuantity, modbus.ReadWriteOnWriteRegQuantityMin, modbus.ReadWriteOnWriteRegQuantityMax)
- }
- if len(value) != int(writeQuantity*2) {
- return nil, fmt.Errorf("modbus: value length '%v' does not twice as write quantity '%v'",
- len(value), writeQuantity)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeReadWriteMultipleRegisters,
- Data: pduDataBlockSuffix(value, readAddress, readQuantity, writeAddress, writeQuantity),
- })
- if err != nil {
- return nil, err
- }
- if int(response.Data[0]) != (len(response.Data) - 1) {
- return nil, fmt.Errorf("modbus: response data size '%v' does not match count '%v'",
- len(response.Data)-1, response.Data[0])
- }
- return response.Data[1:], nil
- }
- // ReadWriteMultipleRegisters Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x17)
- // Read starting address quantity: 2 bytes
- // Quantity to read : 2 bytes
- // Write starting address: 2 bytes
- // Quantity to write : 2 bytes
- // Write byte count : 1 byte
- // Write registers value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x17)
- // Byte count : 1 byte
- // Read registers value : N 2-bytes
- func (o *ModbusRtu) ReadWriteMultipleRegisters(slaveID byte, readAddress, readQuantity,
- writeAddress, writeQuantity uint16, value []byte) ([]uint16, error) {
- b, err := o.ReadWriteMultipleRegistersBytes(slaveID, readAddress, readQuantity,
- writeAddress, writeQuantity, value)
- if err != nil {
- return nil, err
- }
- return bytes2Uint16(b), nil
- }
- // ReadFIFOQueue Request:
- //
- // Slave ID : 1 byte
- // Function code : 1 byte (0x18)
- // FIFO pointer address : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x18)
- // Byte count : 2 bytes only include follow
- // FIFO count : 2 bytes (<=31)
- // FIFO value register : Nx2 bytes
- func (o *ModbusRtu) ReadFIFOQueue(slaveID byte, address uint16) ([]byte, error) {
- if slaveID < modbus.AddressMin || slaveID > modbus.AddressMax {
- return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
- slaveID, modbus.AddressMin, modbus.AddressMax)
- }
- response, err := o.Send(slaveID, modbus.ProtocolDataUnit{
- FuncCode: modbus.FuncCodeReadFIFOQueue,
- Data: uint162Bytes(address),
- })
- switch {
- case err != nil:
- return nil, err
- case len(response.Data) < 4:
- return nil, fmt.Errorf("modbus: response data size '%v' is less than expected '%v'",
- len(response.Data), 4)
- case len(response.Data)-2 != int(binary.BigEndian.Uint16(response.Data)):
- return nil, fmt.Errorf("modbus: response data size '%v' does not match count '%v'",
- len(response.Data)-2, binary.BigEndian.Uint16(response.Data))
- case int(binary.BigEndian.Uint16(response.Data[2:])) > 31:
- return nil, fmt.Errorf("modbus: fifo count '%v' is greater than expected '%v'",
- binary.BigEndian.Uint16(response.Data[2:]), 31)
- }
- return response.Data[4:], nil
- }
- // uint162Bytes creates a sequence of uint16 data.
- func uint162Bytes(value ...uint16) []byte {
- data := make([]byte, 2*len(value))
- for i, v := range value {
- binary.BigEndian.PutUint16(data[i*2:], v)
- }
- return data
- }
- // bytes2Uint16 bytes convert to uint16 for register
- func bytes2Uint16(buf []byte) []uint16 {
- data := make([]uint16, 0, len(buf)/2)
- for i := 0; i < len(buf)/2; i++ {
- data = append(data, binary.BigEndian.Uint16(buf[i*2:]))
- }
- return data
- }
- // pduDataBlockSuffix creates a sequence of uint16 data and append the suffix plus its length.
- func pduDataBlockSuffix(suffix []byte, value ...uint16) []byte {
- length := 2 * len(value)
- data := make([]byte, length+1+len(suffix))
- for i, v := range value {
- binary.BigEndian.PutUint16(data[i*2:], v)
- }
- data[length] = uint8(len(suffix))
- copy(data[length+1:], suffix)
- return data
- }
|