From 374dc77a0e7a5b45c8b9bc70dddfbd43a2cf762d Mon Sep 17 00:00:00 2001 From: walker Date: Mon, 11 Dec 2023 17:28:52 +0800 Subject: [PATCH] =?UTF-8?q?modbus=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AE=BE=E7=BD=AE=E5=AD=97=E8=8A=82=E5=BA=8F=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=20modbus=E6=9C=8D=E5=8A=A1=E9=85=8D=E7=BD=AE=E5=88=A0?= =?UTF-8?q?=E9=99=A4id=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 3 +-- modbus/api.go | 9 +++++++ modbus/client.go | 13 ++++++++- proto/src/dc.proto | 3 +-- service/modbus_dc_service.go | 52 ++++++++++++++++++++++-------------- service/proto/dc.pb.go | 15 +++-------- 6 files changed, 58 insertions(+), 37 deletions(-) diff --git a/main.go b/main.go index 8a79d5a..c8a82c7 100644 --- a/main.go +++ b/main.go @@ -18,9 +18,8 @@ func main() { }))) dc := model.NewDC(make([]byte, 2), make([]byte, 2)) mds, err := service.NewModbusDcService(&proto.ModbusConfig{ - Id: 1, Url: "tcp://127.0.0.1:502", - UnitId: 1, + UnitId: 2, Timeout: 500, Interval: 1000, Mapping: []*proto.ModbusDcMapping{ diff --git a/modbus/api.go b/modbus/api.go index 75703be..3ac8343 100644 --- a/modbus/api.go +++ b/modbus/api.go @@ -1,5 +1,12 @@ package modbus +type Endianness int32 + +const ( + BigEndian Endianness = 0 + LittleEndian Endianness = 1 +) + // 在modbus协议中,主机正常是客户端 type MasterClient interface { // 启动 @@ -12,6 +19,8 @@ type MasterClient interface { IsConnected() bool // 设置从机id SetUnitId(id uint8) error + // 设置字节序 + SetEndianness(endianness Endianness) error // 读线圈,位操作,功能码为0x01 ReadCoil(addr uint16, quantity uint16) ([]bool, error) // 读一个线圈 diff --git a/modbus/client.go b/modbus/client.go index ace86e1..b87e9ea 100644 --- a/modbus/client.go +++ b/modbus/client.go @@ -51,6 +51,17 @@ func (c *client) SetUnitId(id uint8) error { return c.cli.SetUnitId(id) } +func (c *client) SetEndianness(endianness Endianness) error { + switch endianness { + case BigEndian: + return c.cli.SetEncoding(modbus.BIG_ENDIAN, modbus.HIGH_WORD_FIRST) + case LittleEndian: + return c.cli.SetEncoding(modbus.LITTLE_ENDIAN, modbus.HIGH_WORD_FIRST) + default: + return fmt.Errorf("unknown endianness value %v", endianness) + } +} + func (c *client) Start() error { c.started = true return nil @@ -73,7 +84,7 @@ func (c *client) connManage(ctx context.Context) { if c.started && !c.connected { // 已经启动, 尝试重连 err := c.cli.Open() if err != nil { - slog.Error("modbus客户端尝试连接失败", "url", c.url, "err", err) + slog.Error("modbus客户端连接失败", "url", c.url, "err", err) } else { c.connected = true slog.Info("modbus客户端连接成功", "url", c.url) diff --git a/proto/src/dc.proto b/proto/src/dc.proto index 308cd46..2ad2903 100644 --- a/proto/src/dc.proto +++ b/proto/src/dc.proto @@ -31,7 +31,6 @@ message Modbus { // modbus任务配置 message ModbusConfig { - uint32 id = 1; string url = 2; // 连接地址 uint32 unitId = 3; // 从机unitId Modbus.Endianness endianness = 4; // 16位寄存器字节序 @@ -51,7 +50,7 @@ message ModbusDcMapping { Modbus.Function function = 1; // 功能 uint32 addr = 2; // 起始地址,当功能为位功能时,表示起始位地址,当功能为寄存器功能时,表示起始字(2个字节)地址 uint32 quantity = 3; // 数量,当功能为位功能时,表示位数,当功能为寄存器功能时,表示字(2个字节)数 - Modbus.WriteStrategy writeStrategy = 4; // 当功能为写入类功能时(不包含读写类功能),写策略 + Modbus.WriteStrategy writeStrategy = 4; // 当功能为写入类功能时,写策略 DataType type = 5; // 对应数据类型 uint32 start = 6; // 驱动/采集码表中的起始下标,当功能为位功能时,表示起始位,当功能为寄存器功能时,表示起始字节 } diff --git a/service/modbus_dc_service.go b/service/modbus_dc_service.go index 6403c9a..fc17f92 100644 --- a/service/modbus_dc_service.go +++ b/service/modbus_dc_service.go @@ -13,6 +13,7 @@ import ( sproto "joylink.club/iot/service/proto" ) +// Modbus驱动采集服务 type modbusDcService struct { config *sproto.ModbusConfig cli modbus.MasterClient @@ -36,6 +37,8 @@ func NewModbusDcService(config *sproto.ModbusConfig, dc model.DC) (IotService, e return nil, errors.Join(err, fmt.Errorf("modbus客户端创建失败,url=%s", config.Url)) } cli.SetUnitId(uint8(config.UnitId)) + cli.SetEndianness(convertEndianness(config.Endianness)) + cli.Start() s := &modbusDcService{ config: config, cli: cli, @@ -58,23 +61,23 @@ func (m *modbusDcService) initOnUpdateTask() { } m.dc.On(et, func(d model.DC) { if !m.cli.IsConnected() { - slog.Warn("Modbus驱动采集服务数据更新写入失败,modbus客户端未连接", "id", m.config.Id, "url", m.config.Url, "Function", mdm.Function) + slog.Warn("Modbus驱动采集服务数据更新写入失败,modbus客户端未连接", "url", m.config.Url, "Function", mdm.Function) return } switch mdm.Function { case sproto.Modbus_WriteCoil, sproto.Modbus_WriteCoils, sproto.Modbus_RWCoils: err := m.cli.WriteCoils(uint16(mdm.Addr), m.GetDcBits(mdm)) if err != nil { - slog.Error("Modbus驱动采集服务写入线圈失败", "id", m.config.Id, "url", m.config.Url, "error", err, "Function", mdm.Function) + slog.Error("Modbus驱动采集服务写入线圈失败", "url", m.config.Url, "error", err, "Function", mdm.Function) } else { - slog.Info("Modbus驱动采集服务写入线圈成功", "id", m.config.Id, "url", m.config.Url, "Function", mdm.Function) + slog.Info("Modbus驱动采集服务写入线圈成功", "url", m.config.Url, "Function", mdm.Function) } case sproto.Modbus_WriteRegister, sproto.Modbus_WriteRegisters, sproto.Modbus_RWRegisters: err := m.cli.WriteRegisterBytes(uint16(mdm.Addr), m.GetDcBytes(mdm)) if err != nil { - slog.Error("Modbus驱动采集服务写入寄存器失败", "id", m.config.Id, "url", m.config.Url, "error", err, "Function", mdm.Function) + slog.Error("Modbus驱动采集服务写入寄存器失败", "url", m.config.Url, "error", err, "Function", mdm.Function) } else { - slog.Info("Modbus驱动采集服务写入寄存器成功", "id", m.config.Id, "url", m.config.Url, "Function", mdm.Function) + slog.Info("Modbus驱动采集服务写入寄存器成功", "url", m.config.Url, "Function", mdm.Function) } } }) @@ -92,11 +95,10 @@ func isWriteFunction(modbus_Function sproto.Modbus_Function) bool { } func (m *modbusDcService) run(ctx context.Context) { - m.cli.Start() for { select { case <-ctx.Done(): - slog.Info("Modbus驱动采集服务退出", "id", m.config.Id, "url", m.config.Url) + slog.Info("Modbus驱动采集服务退出", "url", m.config.Url) return default: } @@ -112,45 +114,45 @@ func (m *modbusDcService) mappingTaskExecute() { case sproto.Modbus_ReadCoil, sproto.Modbus_RWCoils: data, err := m.cli.ReadCoil(uint16(mdm.Addr), uint16(mdm.Quantity)) if err != nil { - slog.Error("Modbus驱动采集服务读取线圈失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务读取线圈失败", "url", m.config.Url, "error", err) continue } err = m.updateDcByBits(mdm, data) if err != nil { - slog.Error("Modbus驱动采集服务更新驱采数据失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务更新驱采数据失败", "url", m.config.Url, "error", err) continue } case sproto.Modbus_ReadDiscreteInput: data, err := m.cli.ReadDiscreteInput(uint16(mdm.Addr), uint16(mdm.Quantity)) if err != nil { - slog.Error("Modbus驱动采集服务读取离散输入失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务读取离散输入失败", "url", m.config.Url, "error", err) continue } err = m.updateDcByBits(mdm, data) if err != nil { - slog.Error("Modbus驱动采集服务更新驱采数据失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务更新驱采数据失败", "url", m.config.Url, "error", err) continue } case sproto.Modbus_ReadHoldingRegister, sproto.Modbus_RWRegisters: data, err := m.cli.ReadHoldingRegisterBytes(uint16(mdm.Addr), uint16(mdm.Quantity*2)) if err != nil { - slog.Error("Modbus驱动采集服务读取保持寄存器失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务读取保持寄存器失败", "url", m.config.Url, "error", err) continue } err = m.updateDcByBytes(mdm, data) if err != nil { - slog.Error("Modbus驱动采集服务更新驱采数据失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务更新驱采数据失败", "url", m.config.Url, "error", err) continue } case sproto.Modbus_ReadInputRegister: data, err := m.cli.ReadInputRegisterBytes(uint16(mdm.Addr), uint16(mdm.Quantity*2)) if err != nil { - slog.Error("Modbus驱动采集服务读取输入寄存器失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务读取输入寄存器失败", "url", m.config.Url, "error", err) continue } err = m.updateDcByBytes(mdm, data) if err != nil { - slog.Error("Modbus驱动采集服务更新驱采数据失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务更新驱采数据失败", "url", m.config.Url, "error", err) continue } case sproto.Modbus_WriteCoil: @@ -158,7 +160,7 @@ func (m *modbusDcService) mappingTaskExecute() { bits := m.GetDcBits(mdm) err := m.cli.WriteCoil(uint16(mdm.Addr), bits[0]) if err != nil { - slog.Error("Modbus驱动采集服务写单线圈失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务写单线圈失败", "url", m.config.Url, "error", err) continue } } @@ -167,7 +169,7 @@ func (m *modbusDcService) mappingTaskExecute() { bits := m.GetDcBits(mdm) err := m.cli.WriteCoils(uint16(mdm.Addr), bits) if err != nil { - slog.Error("Modbus驱动采集服务写多线圈失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务写多线圈失败", "url", m.config.Url, "error", err) continue } } @@ -176,7 +178,7 @@ func (m *modbusDcService) mappingTaskExecute() { data := m.GetDcBytes(mdm) err := m.cli.WriteRegisterBytes(uint16(mdm.Addr), data) if err != nil { - slog.Error("Modbus驱动采集服务写单寄存器失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务写单寄存器失败", "url", m.config.Url, "error", err) continue } } @@ -185,14 +187,14 @@ func (m *modbusDcService) mappingTaskExecute() { data := m.GetDcBytes(mdm) err := m.cli.WriteRegisterBytes(uint16(mdm.Addr), data) if err != nil { - slog.Error("Modbus驱动采集服务写多寄存器失败", "id", m.config.Id, "url", m.config.Url, "error", err) + slog.Error("Modbus驱动采集服务写多寄存器失败", "url", m.config.Url, "error", err) continue } } } } } else { - slog.Error("Modbus驱动采集服务映射任务执行失败,Modbus未连接", "id", m.config.Id, "url", m.config.Url) + slog.Error("Modbus驱动采集服务映射任务执行失败,Modbus未连接", "url", m.config.Url) } } @@ -267,3 +269,13 @@ func (m *modbusDcService) Stop() error { modbus.DeleteClient(m.config.Url) return nil } + +func convertEndianness(endianness sproto.Modbus_Endianness) modbus.Endianness { + switch endianness { + case sproto.Modbus_BigEndian: + return modbus.BigEndian + case sproto.Modbus_LittleEndian: + return modbus.LittleEndian + } + return modbus.BigEndian +} diff --git a/service/proto/dc.pb.go b/service/proto/dc.pb.go index 2935260..72274db 100644 --- a/service/proto/dc.pb.go +++ b/service/proto/dc.pb.go @@ -276,7 +276,6 @@ type ModbusConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` // 连接地址 UnitId uint32 `protobuf:"varint,3,opt,name=unitId,proto3" json:"unitId,omitempty"` // 从机unitId Endianness Modbus_Endianness `protobuf:"varint,4,opt,name=endianness,proto3,enum=iot_service.Modbus_Endianness" json:"endianness,omitempty"` // 16位寄存器字节序 @@ -317,13 +316,6 @@ func (*ModbusConfig) Descriptor() ([]byte, []int) { return file_dc_proto_rawDescGZIP(), []int{1} } -func (x *ModbusConfig) GetId() uint32 { - if x != nil { - return x.Id - } - return 0 -} - func (x *ModbusConfig) GetUrl() string { if x != nil { return x.Url @@ -375,7 +367,7 @@ type ModbusDcMapping struct { Function Modbus_Function `protobuf:"varint,1,opt,name=function,proto3,enum=iot_service.Modbus_Function" json:"function,omitempty"` // 功能 Addr uint32 `protobuf:"varint,2,opt,name=addr,proto3" json:"addr,omitempty"` // 起始地址,当功能为位功能时,表示起始位地址,当功能为寄存器功能时,表示起始字(2个字节)地址 Quantity uint32 `protobuf:"varint,3,opt,name=quantity,proto3" json:"quantity,omitempty"` // 数量,当功能为位功能时,表示位数,当功能为寄存器功能时,表示字(2个字节)数 - WriteStrategy Modbus_WriteStrategy `protobuf:"varint,4,opt,name=writeStrategy,proto3,enum=iot_service.Modbus_WriteStrategy" json:"writeStrategy,omitempty"` // 当功能为写入类功能时(不包含读写类功能),写策略 + WriteStrategy Modbus_WriteStrategy `protobuf:"varint,4,opt,name=writeStrategy,proto3,enum=iot_service.Modbus_WriteStrategy" json:"writeStrategy,omitempty"` // 当功能为写入类功能时,写策略 Type DataType `protobuf:"varint,5,opt,name=type,proto3,enum=iot_service.DataType" json:"type,omitempty"` // 对应数据类型 Start uint32 `protobuf:"varint,6,opt,name=start,proto3" json:"start,omitempty"` // 驱动/采集码表中的起始下标,当功能为位功能时,表示起始位,当功能为寄存器功能时,表示起始字节 } @@ -477,9 +469,8 @@ var file_dc_proto_rawDesc = []byte{ 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x22, 0x2d, 0x0a, 0x0a, 0x45, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x42, 0x69, 0x67, 0x45, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x69, 0x74, 0x74, 0x6c, 0x65, 0x45, - 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x10, 0x01, 0x22, 0xf6, 0x01, 0x0a, 0x0c, 0x4d, 0x6f, 0x64, 0x62, - 0x75, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x10, 0x01, 0x22, 0xe6, 0x01, 0x0a, 0x0c, 0x4d, 0x6f, 0x64, 0x62, + 0x75, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x6e, 0x65, 0x73, 0x73,