From b611f132f128f52ad908abe2650b81a424da9b9d Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Tue, 7 Oct 2025 16:14:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0proto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/app/webhook/chirp_stack.go | 18 +- .../domain/device/general_device_service.go | 21 +- internal/domain/device/proto/device.pb.go | 284 +++++++++--------- internal/domain/device/proto/device.proto | 42 +-- 4 files changed, 179 insertions(+), 186 deletions(-) diff --git a/internal/app/webhook/chirp_stack.go b/internal/app/webhook/chirp_stack.go index de07597..2463ffc 100644 --- a/internal/app/webhook/chirp_stack.go +++ b/internal/app/webhook/chirp_stack.go @@ -207,16 +207,20 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) { return } - // 3.3 检查是否是采集响应 - if instruction.Method != proto.MethodType_COLLECT { - c.logger.Infof("收到一个非采集响应的上行指令 (Method: %s),无需处理。", instruction.Method.String()) + // 3.3 使用 type switch 从 oneof payload 中提取 CollectResult + var collectResp *proto.CollectResult + switch p := instruction.GetPayload().(type) { + case *proto.Instruction_CollectResult: + collectResp = p.CollectResult + default: + // 如果上行的数据不是采集结果,记录日志并忽略 + c.logger.Infof("收到一个非采集响应的上行指令 (Type: %T),无需处理。", p) return } - // 3.4 解包内层 CollectResult - var collectResp proto.CollectResult - if err := instruction.Data.UnmarshalTo(&collectResp); err != nil { - c.logger.Errorf("解包数据信息失败: %v", err) + // 检查 collectResp 是否为 nil,虽然在 type switch 成功的情况下不太可能 + if collectResp == nil { + c.logger.Errorf("从 Instruction 中提取的 CollectResult 为 nil") return } diff --git a/internal/domain/device/general_device_service.go b/internal/domain/device/general_device_service.go index d838476..8fa2894 100644 --- a/internal/domain/device/general_device_service.go +++ b/internal/domain/device/general_device_service.go @@ -14,7 +14,6 @@ import ( "github.com/google/uuid" gproto "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" ) type GeneralDeviceService struct { @@ -90,14 +89,10 @@ func (g *GeneralDeviceService) Switch(device *models.Device, action DeviceAction CommandBytes: modbusCommandBytes, } - data, err := anypb.New(raw485Cmd) - if err != nil { - return fmt.Errorf("创建 Raw485Command Any Protobuf 失败: %w", err) - } - instruction := &proto.Instruction{ - Method: proto.MethodType_INSTRUCTION, // 使用通用指令类型 - Data: data, + Payload: &proto.Instruction_Raw_485Command{ + Raw_485Command: raw485Cmd, + }, } message, err := gproto.Marshal(instruction) @@ -224,14 +219,10 @@ func (g *GeneralDeviceService) Collect(regionalControllerID uint, devicesToColle CorrelationId: correlationID, Tasks: collectTasks, } - anyData, err := anypb.New(batchCmd) - if err != nil { - g.logger.Errorf("创建 Any Protobuf 失败 (CorrelationID: %s): %v", correlationID, err) - return err - } instruction := &proto.Instruction{ - Method: proto.MethodType_COLLECT, // 使用 COLLECT 指令类型 - Data: anyData, + Payload: &proto.Instruction_BatchCollectCommand{ + BatchCollectCommand: batchCmd, + }, } payload, err := gproto.Marshal(instruction) if err != nil { diff --git a/internal/domain/device/proto/device.pb.go b/internal/domain/device/proto/device.pb.go index 712f749..25fddfe 100644 --- a/internal/domain/device/proto/device.pb.go +++ b/internal/domain/device/proto/device.pb.go @@ -9,7 +9,6 @@ package proto import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - anypb "google.golang.org/protobuf/types/known/anypb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -22,53 +21,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// 指令类型 -type MethodType int32 - -const ( - MethodType_INSTRUCTION MethodType = 0 // 下发指令 - MethodType_COLLECT MethodType = 1 // 批量采集 -) - -// Enum value maps for MethodType. -var ( - MethodType_name = map[int32]string{ - 0: "INSTRUCTION", - 1: "COLLECT", - } - MethodType_value = map[string]int32{ - "INSTRUCTION": 0, - "COLLECT": 1, - } -) - -func (x MethodType) Enum() *MethodType { - p := new(MethodType) - *p = x - return p -} - -func (x MethodType) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (MethodType) Descriptor() protoreflect.EnumDescriptor { - return file_device_proto_enumTypes[0].Descriptor() -} - -func (MethodType) Type() protoreflect.EnumType { - return &file_device_proto_enumTypes[0] -} - -func (x MethodType) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use MethodType.Descriptor instead. -func (MethodType) EnumDescriptor() ([]byte, []int) { - return file_device_proto_rawDescGZIP(), []int{0} -} - // 平台生成的原始485指令,单片机直接发送到总线 type Raw485Command struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -122,63 +74,8 @@ func (x *Raw485Command) GetCommandBytes() []byte { return nil } -// 指令 (所有空中数据都会被包装在这里面) -// data字段现在可以包含 Raw485Command,表示平台生成的原始485指令。 -type Instruction struct { - state protoimpl.MessageState `protogen:"open.v1"` - Method MethodType `protobuf:"varint,1,opt,name=method,proto3,enum=device.MethodType" json:"method,omitempty"` - Data *anypb.Any `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` // 可以是 Switch, Raw485Command 等 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Instruction) Reset() { - *x = Instruction{} - mi := &file_device_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Instruction) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Instruction) ProtoMessage() {} - -func (x *Instruction) ProtoReflect() protoreflect.Message { - mi := &file_device_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Instruction.ProtoReflect.Descriptor instead. -func (*Instruction) Descriptor() ([]byte, []int) { - return file_device_proto_rawDescGZIP(), []int{1} -} - -func (x *Instruction) GetMethod() MethodType { - if x != nil { - return x.Method - } - return MethodType_INSTRUCTION -} - -func (x *Instruction) GetData() *anypb.Any { - if x != nil { - return x.Data - } - return nil -} - // BatchCollectCommand -// 用于在平台内部构建一个完整的、包含所有元数据的批量采集任务。 -// 这个消息本身不会被发送到设备。 +// 一个完整的、包含所有元数据的批量采集任务。 type BatchCollectCommand struct { state protoimpl.MessageState `protogen:"open.v1"` CorrelationId string `protobuf:"bytes,1,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"` // 用于关联请求和响应的唯一ID @@ -189,7 +86,7 @@ type BatchCollectCommand struct { func (x *BatchCollectCommand) Reset() { *x = BatchCollectCommand{} - mi := &file_device_proto_msgTypes[2] + mi := &file_device_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -201,7 +98,7 @@ func (x *BatchCollectCommand) String() string { func (*BatchCollectCommand) ProtoMessage() {} func (x *BatchCollectCommand) ProtoReflect() protoreflect.Message { - mi := &file_device_proto_msgTypes[2] + mi := &file_device_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -214,7 +111,7 @@ func (x *BatchCollectCommand) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchCollectCommand.ProtoReflect.Descriptor instead. func (*BatchCollectCommand) Descriptor() ([]byte, []int) { - return file_device_proto_rawDescGZIP(), []int{2} + return file_device_proto_rawDescGZIP(), []int{1} } func (x *BatchCollectCommand) GetCorrelationId() string { @@ -232,7 +129,7 @@ func (x *BatchCollectCommand) GetTasks() []*CollectTask { } // CollectTask -// 定义了单个采集任务的“意图”。现在直接包含平台生成的原始485指令,并带上总线号。 +// 定义了单个采集任务的“意图”。 type CollectTask struct { state protoimpl.MessageState `protogen:"open.v1"` Command *Raw485Command `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` // 平台生成的原始485指令 @@ -242,7 +139,7 @@ type CollectTask struct { func (x *CollectTask) Reset() { *x = CollectTask{} - mi := &file_device_proto_msgTypes[3] + mi := &file_device_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -254,7 +151,7 @@ func (x *CollectTask) String() string { func (*CollectTask) ProtoMessage() {} func (x *CollectTask) ProtoReflect() protoreflect.Message { - mi := &file_device_proto_msgTypes[3] + mi := &file_device_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -267,7 +164,7 @@ func (x *CollectTask) ProtoReflect() protoreflect.Message { // Deprecated: Use CollectTask.ProtoReflect.Descriptor instead. func (*CollectTask) Descriptor() ([]byte, []int) { - return file_device_proto_rawDescGZIP(), []int{3} + return file_device_proto_rawDescGZIP(), []int{2} } func (x *CollectTask) GetCommand() *Raw485Command { @@ -289,7 +186,7 @@ type CollectResult struct { func (x *CollectResult) Reset() { *x = CollectResult{} - mi := &file_device_proto_msgTypes[4] + mi := &file_device_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -301,7 +198,7 @@ func (x *CollectResult) String() string { func (*CollectResult) ProtoMessage() {} func (x *CollectResult) ProtoReflect() protoreflect.Message { - mi := &file_device_proto_msgTypes[4] + mi := &file_device_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -314,7 +211,7 @@ func (x *CollectResult) ProtoReflect() protoreflect.Message { // Deprecated: Use CollectResult.ProtoReflect.Descriptor instead. func (*CollectResult) Descriptor() ([]byte, []int) { - return file_device_proto_rawDescGZIP(), []int{4} + return file_device_proto_rawDescGZIP(), []int{3} } func (x *CollectResult) GetCorrelationId() string { @@ -331,18 +228,116 @@ func (x *CollectResult) GetValues() []float32 { return nil } +// 指令 (所有从平台下发到设备的数据都应该被包装在这里面) +// 使用 oneof 来替代 google.protobuf.Any,这是嵌入式环境下的标准做法。 +// 它高效、类型安全,且只解码一次。 +type Instruction struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *Instruction_Raw_485Command + // *Instruction_BatchCollectCommand + // *Instruction_CollectResult + Payload isInstruction_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Instruction) Reset() { + *x = Instruction{} + mi := &file_device_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Instruction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Instruction) ProtoMessage() {} + +func (x *Instruction) ProtoReflect() protoreflect.Message { + mi := &file_device_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Instruction.ProtoReflect.Descriptor instead. +func (*Instruction) Descriptor() ([]byte, []int) { + return file_device_proto_rawDescGZIP(), []int{4} +} + +func (x *Instruction) GetPayload() isInstruction_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *Instruction) GetRaw_485Command() *Raw485Command { + if x != nil { + if x, ok := x.Payload.(*Instruction_Raw_485Command); ok { + return x.Raw_485Command + } + } + return nil +} + +func (x *Instruction) GetBatchCollectCommand() *BatchCollectCommand { + if x != nil { + if x, ok := x.Payload.(*Instruction_BatchCollectCommand); ok { + return x.BatchCollectCommand + } + } + return nil +} + +func (x *Instruction) GetCollectResult() *CollectResult { + if x != nil { + if x, ok := x.Payload.(*Instruction_CollectResult); ok { + return x.CollectResult + } + } + return nil +} + +type isInstruction_Payload interface { + isInstruction_Payload() +} + +type Instruction_Raw_485Command struct { + Raw_485Command *Raw485Command `protobuf:"bytes,1,opt,name=raw_485_command,json=raw485Command,proto3,oneof"` +} + +type Instruction_BatchCollectCommand struct { + BatchCollectCommand *BatchCollectCommand `protobuf:"bytes,2,opt,name=batch_collect_command,json=batchCollectCommand,proto3,oneof"` +} + +type Instruction_CollectResult struct { + CollectResult *CollectResult `protobuf:"bytes,3,opt,name=collect_result,json=collectResult,proto3,oneof"` // ADDED:用于上行数据 +} + +func (*Instruction_Raw_485Command) isInstruction_Payload() {} + +func (*Instruction_BatchCollectCommand) isInstruction_Payload() {} + +func (*Instruction_CollectResult) isInstruction_Payload() {} + var File_device_proto protoreflect.FileDescriptor const file_device_proto_rawDesc = "" + "\n" + - "\fdevice.proto\x12\x06device\x1a\x19google/protobuf/any.proto\"S\n" + + "\fdevice.proto\x12\x06device\"S\n" + "\rRaw485Command\x12\x1d\n" + "\n" + "bus_number\x18\x01 \x01(\x05R\tbusNumber\x12#\n" + - "\rcommand_bytes\x18\x02 \x01(\fR\fcommandBytes\"c\n" + - "\vInstruction\x12*\n" + - "\x06method\x18\x01 \x01(\x0e2\x12.device.MethodTypeR\x06method\x12(\n" + - "\x04data\x18\x02 \x01(\v2\x14.google.protobuf.AnyR\x04data\"g\n" + + "\rcommand_bytes\x18\x02 \x01(\fR\fcommandBytes\"g\n" + "\x13BatchCollectCommand\x12%\n" + "\x0ecorrelation_id\x18\x01 \x01(\tR\rcorrelationId\x12)\n" + "\x05tasks\x18\x02 \x03(\v2\x13.device.CollectTaskR\x05tasks\">\n" + @@ -350,11 +345,12 @@ const file_device_proto_rawDesc = "" + "\acommand\x18\x02 \x01(\v2\x15.device.Raw485CommandR\acommand\"N\n" + "\rCollectResult\x12%\n" + "\x0ecorrelation_id\x18\x01 \x01(\tR\rcorrelationId\x12\x16\n" + - "\x06values\x18\x02 \x03(\x02R\x06values**\n" + - "\n" + - "MethodType\x12\x0f\n" + - "\vINSTRUCTION\x10\x00\x12\v\n" + - "\aCOLLECT\x10\x01B\x1eZ\x1cinternal/domain/device/protob\x06proto3" + "\x06values\x18\x02 \x03(\x02R\x06values\"\xec\x01\n" + + "\vInstruction\x12?\n" + + "\x0fraw_485_command\x18\x01 \x01(\v2\x15.device.Raw485CommandH\x00R\rraw485Command\x12Q\n" + + "\x15batch_collect_command\x18\x02 \x01(\v2\x1b.device.BatchCollectCommandH\x00R\x13batchCollectCommand\x12>\n" + + "\x0ecollect_result\x18\x03 \x01(\v2\x15.device.CollectResultH\x00R\rcollectResultB\t\n" + + "\apayloadB\x1eZ\x1cinternal/domain/device/protob\x06proto3" var ( file_device_proto_rawDescOnce sync.Once @@ -368,27 +364,25 @@ func file_device_proto_rawDescGZIP() []byte { return file_device_proto_rawDescData } -var file_device_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_device_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_device_proto_goTypes = []any{ - (MethodType)(0), // 0: device.MethodType - (*Raw485Command)(nil), // 1: device.Raw485Command - (*Instruction)(nil), // 2: device.Instruction - (*BatchCollectCommand)(nil), // 3: device.BatchCollectCommand - (*CollectTask)(nil), // 4: device.CollectTask - (*CollectResult)(nil), // 5: device.CollectResult - (*anypb.Any)(nil), // 6: google.protobuf.Any + (*Raw485Command)(nil), // 0: device.Raw485Command + (*BatchCollectCommand)(nil), // 1: device.BatchCollectCommand + (*CollectTask)(nil), // 2: device.CollectTask + (*CollectResult)(nil), // 3: device.CollectResult + (*Instruction)(nil), // 4: device.Instruction } var file_device_proto_depIdxs = []int32{ - 0, // 0: device.Instruction.method:type_name -> device.MethodType - 6, // 1: device.Instruction.data:type_name -> google.protobuf.Any - 4, // 2: device.BatchCollectCommand.tasks:type_name -> device.CollectTask - 1, // 3: device.CollectTask.command:type_name -> device.Raw485Command - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 2, // 0: device.BatchCollectCommand.tasks:type_name -> device.CollectTask + 0, // 1: device.CollectTask.command:type_name -> device.Raw485Command + 0, // 2: device.Instruction.raw_485_command:type_name -> device.Raw485Command + 1, // 3: device.Instruction.batch_collect_command:type_name -> device.BatchCollectCommand + 3, // 4: device.Instruction.collect_result:type_name -> device.CollectResult + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_device_proto_init() } @@ -396,19 +390,23 @@ func file_device_proto_init() { if File_device_proto != nil { return } + file_device_proto_msgTypes[4].OneofWrappers = []any{ + (*Instruction_Raw_485Command)(nil), + (*Instruction_BatchCollectCommand)(nil), + (*Instruction_CollectResult)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_device_proto_rawDesc), len(file_device_proto_rawDesc)), - NumEnums: 1, + NumEnums: 0, NumMessages: 5, NumExtensions: 0, NumServices: 0, }, GoTypes: file_device_proto_goTypes, DependencyIndexes: file_device_proto_depIdxs, - EnumInfos: file_device_proto_enumTypes, MessageInfos: file_device_proto_msgTypes, }.Build() File_device_proto = out.File diff --git a/internal/domain/device/proto/device.proto b/internal/domain/device/proto/device.proto index a906255..666f68a 100644 --- a/internal/domain/device/proto/device.proto +++ b/internal/domain/device/proto/device.proto @@ -2,17 +2,11 @@ syntax = "proto3"; package device; -import "google/protobuf/any.proto"; +// import "google/protobuf/any.proto"; // REMOVED: Not suitable for embedded systems. option go_package = "internal/domain/device/proto"; -// --- 通用指令结构 --- - -// 指令类型 -enum MethodType { - INSTRUCTION = 0; // 下发指令 - COLLECT = 1; // 批量采集 -} +// --- Concrete Command & Data Structures --- // 平台生成的原始485指令,单片机直接发送到总线 message Raw485Command { @@ -20,25 +14,15 @@ message Raw485Command { bytes command_bytes = 2; // 原始485指令的字节数组 } -// 指令 (所有空中数据都会被包装在这里面) -// data字段现在可以包含 Raw485Command,表示平台生成的原始485指令。 -message Instruction { - MethodType method = 1; - google.protobuf.Any data = 2; // 可以是 Switch, Raw485Command 等 -} - -// --- 批量采集相关结构 --- - // BatchCollectCommand -// 用于在平台内部构建一个完整的、包含所有元数据的批量采集任务。 -// 这个消息本身不会被发送到设备。 +// 一个完整的、包含所有元数据的批量采集任务。 message BatchCollectCommand { string correlation_id = 1; // 用于关联请求和响应的唯一ID repeated CollectTask tasks = 2; // 采集任务列表 } // CollectTask -// 定义了单个采集任务的“意图”。现在直接包含平台生成的原始485指令,并带上总线号。 +// 定义了单个采集任务的“意图”。 message CollectTask { Raw485Command command = 2; // 平台生成的原始485指令 } @@ -48,4 +32,20 @@ message CollectTask { message CollectResult { string correlation_id = 1; // 从下行指令中原样返回的关联ID repeated float values = 2; // 按预定顺序排列的采集值 -} \ No newline at end of file +} + + +// --- Main Downlink Instruction Wrapper --- + +// 指令 (所有从平台下发到设备的数据都应该被包装在这里面) +// 使用 oneof 来替代 google.protobuf.Any,这是嵌入式环境下的标准做法。 +// 它高效、类型安全,且只解码一次。 +message Instruction { + oneof payload { + Raw485Command raw_485_command = 1; + BatchCollectCommand batch_collect_command = 2; + CollectResult collect_result = 3; // ADDED:用于上行数据 + // 如果未来有其他指令类型,比如开关控制,可以直接在这里添加 + // SwitchCommand switch_command = 3; + } +}