package main import ( "context" "encoding/binary" "errors" "fmt" "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 { return nil } if iot.Protocol == ModbusRtuProtocol { rtu.model = iot } 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 value, ok := mapRtuUploadManager.Load(o.devInfo.DevCode); ok { uploadManager := value.(*RtuUploadManager) if uploadManager != nil { uploadManager.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 { return } iot, err := loadModel(mi.TID) if err != nil { return } if iot.Protocol == ModbusRtuProtocol { //合法的物模型 o.model = iot o.clearRequest() o.updateRequest() } } 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() { recover() go o.HandleData() }() o.updateRequest() var req *Request for { select { case <-o.ctx.Done(): return case info := <-o.chanDevInfo: o.devInfo = info 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 { 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) ReadDiscrete(cid uint8, address, quality uint16, valBuf []byte) { } func (o *ModbusRtu) ProcReadHoldingRegisters(cid uint8, address, quality uint16, valBuf []byte) { 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 { //索引超长,忽略该项目 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) } } if value, ok := mapRtuUploadManager.Load(o.devInfo.DevCode); ok { uploadManager := value.(*RtuUploadManager) if uploadManager != nil { uploadManager.AddData(dataMap) } } } func (o *ModbusRtu) ProcReadInputRegisters(cid uint8, address, quality uint16, valBuf []byte) { 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 { //索引超长,忽略该项目 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) } } if value, ok := mapRtuUploadManager.Load(o.devInfo.DevCode); ok { uploadManager := value.(*RtuUploadManager) if uploadManager != nil { uploadManager.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 { //离线状态报告 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) } } } } func (o *ModbusRtu) procRequest(req *Request) { var err error var result []byte defer func() { recover() }() req.TxCnt++ switch req.FuncCode { // A bit of access read case modbus.FuncCodeReadCoils: result, err = o.ReadCoils(req.Rtuinfo.DevID, req.Address, req.Quantity) 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) if err == nil { o.ReadDiscrete(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) if err == nil { o.ProcReadHoldingRegisters(req.CID, req.Address, req.Quantity, result) } case modbus.FuncCodeReadInputRegisters: result, err = o.ReadInputRegistersBytes(req.Rtuinfo.DevID, req.Address, req.Quantity) 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() { recover() }() 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: 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 } aduResponse, err := o.SendRecvData(aduRequest) if err != nil { return response, err } rspSlaveID, pdu, err := modbus.DecodeRTUFrame(aduResponse) if err != nil { 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 }