| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139 |
- package main
- import (
- "context"
- "encoding/binary"
- "encoding/hex"
- "errors"
- "fmt"
- "runtime/debug"
- "time"
- "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 {
- util.GetTagLog().Errorf("modbus", "ReloadModel:加载模型[tid=%d]文件发生错误:%s", info.TID, err.Error())
- } else {
- if iot.Protocol == ModbusRtuProtocol {
- rtu.model = iot
- } else {
- util.GetTagLog().Error("modbus", "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 {
- util.GetTagLog().Errorf("modbus", "设备[%s]的物模型[tid=%d]模型文件被删除,下次启动即将生效。", o.devinfo.DevCode, mi.TID)
- return
- }
- util.GetTagLog().Debugf("modbus", "ModbusRtu.UpdateModel2:更新设备[%s]的物模型[%d]", o.devinfo.DevCode, mi.TID)
- iot, err := loadModel(mi.TID)
- if err != nil {
- util.GetTagLog().Errorf("modbus", "ModbusRtu.UpdateModel2:加载模型[%d]文件错误:%s", mi.TID, err.Error())
- return
- }
- if iot.Protocol == ModbusRtuProtocol { //合法的物模型
- o.model = iot
- o.clearRequest()
- o.updateRequest()
- util.GetTagLog().Infof("modbus", "ModbusRtu.UpdateModel2:更新设备[%s]的物模型[%d]成功", o.devinfo.DevCode, mi.TID)
- } else {
- util.GetTagLog().Error("modbus", "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 {
- util.GetTagLog().Error("modbus", "ModbusRtu.HandleData:panic:", err)
- util.GetTagLog().Error("modbus", "stack:", string(debug.Stack()))
- time.Sleep(5 * time.Second)
- go o.HandleData()
- }
- }()
- o.updateRequest()
- var req *Request
- for {
- select {
- case <-o.ctx.Done():
- util.GetTagLog().Errorf("modbus", "设备[%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() {
- if o.model == nil {
- util.GetTagLog().Errorf("modbus", "设备[%s]的物模型为nil,无法更新采集请求,请先下发TID=%d的物模型文件", o.devinfo.DevCode, o.devinfo.TID)
- return
- }
- 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 {
- util.GetTagLog().Errorf("modbus", "给串口[%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) {
- util.GetTagLog().Debugf("modbus", "收到自寄存器地址%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 { //索引超长,忽略该项目
- util.GetTagLog().Errorf("modbus", "物模型[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)
- util.GetTagLog().Debugf("modbus", 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) {
- util.GetTagLog().Debugf("modbus", "收到自寄存器地址%d, 开始的数据:%v, 长度为:%d", address, hex.EncodeToString(valBuf), len(valBuf))
- dataLen := len(valBuf)
- if o.model.Packet[cid].Resplen != uint(dataLen) {
- util.GetTagLog().Errorf("modbus", "ProcReadInputRegisters len no equal")
- return
- }
- dataMap := make(map[uint16]float64)
- for _, v := range o.model.DataUp {
- if v.Cid != cid || v.Len == 0 {
- util.GetTagLog().Errorf("modbus", "ProcReadInputRegisters Cid no equal")
- continue
- }
- if int(v.Start+v.Len) > dataLen { //索引超长,忽略该项目
- util.GetTagLog().Errorf("modbus", "物模型[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)
- }
- }
- util.GetTagLog().Debugf("modbus", "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 {
- util.GetTagLog().Errorf("modbus", "采集设备[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)
- util.GetTagLog().Debugf("modbus", "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 {
- util.GetTagLog().Error("modbus", "procRequest:panic:", err)
- util.GetTagLog().Error("modbus", "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)
- util.GetTagLog().Debugf("modbus", "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)
- util.GetTagLog().Debugf("modbus", "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)
- util.GetTagLog().Debugf("modbus", "ReadHoldingRegistersBytes result:= %s", string(result))
- if err == nil {
- o.ProcReadHoldingRegisters(req.CID, req.Address, req.Quantity, result)
- } else {
- util.GetTagLog().Errorf("modbus", "设备上传网关数据为空!可能原因:未配置正确,请检查配置文件;设备线路或设备本身损坏;网关串口损坏.\n")
- util.GetTagLog().Errorf("modbus", "error:%v\n", err)
- }
- case modbus.FuncCodeReadInputRegisters:
- result, err = o.ReadInputRegistersBytes(req.Rtuinfo.DevID, req.Address, req.Quantity)
- util.GetTagLog().Debugf("modbus", "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 {
- util.GetTagLog().Error("modbus", "WriteData:panic:", err)
- util.GetTagLog().Error("modbus", "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:
- util.GetTagLog().Errorf("modbus", "不支持的功能码:%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
- }
- util.GetTagLog().Debugf("modbus", "发送给设备: %s 的数据: [% x]", o.devinfo.DevCode, aduRequest)
- aduResponse, err := o.SendRecvData(aduRequest)
- util.GetTagLog().Debugf("modbus", "收到的数据: %s 的数据: [% x]", o.devinfo.DevCode, aduResponse)
- if err != nil {
- util.GetTagLog().Debugf("modbus", "ReadHoldingRegistersBytes SendRecvData err:= %v", err)
- return response, err
- }
- rspSlaveID, pdu, err := modbus.DecodeRTUFrame(aduResponse)
- if err != nil {
- util.GetTagLog().Debugf("modbus", "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
- }
|