| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086 |
- 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
- }
|