diff --git a/goTool/README.md b/goTool/README.md new file mode 100644 index 0000000..a60fae7 --- /dev/null +++ b/goTool/README.md @@ -0,0 +1,26 @@ +# goTool + +Host-side UART utilities for Powerpod. + +## version command + +Sends `MessageType.VERSION` (0x03) in a framed UART packet and prints the protobuf response. + +```bash +cd goTool +go mod tidy +go run . -port /dev/ttyUSB0 +``` + +Options: + +- `-port` — serial device (required) +- `-baud` — default `921600` (must match firmware) + +## Regenerate protobuf + +```bash +protoc --go_out=./pb --go_opt=paths=source_relative \ + --go_opt=Muart_messages.proto=powerpod/gotool/pb \ + -I ../main/proto ../main/proto/uart_messages.proto +``` diff --git a/goTool/go.mod b/goTool/go.mod new file mode 100644 index 0000000..80decaf --- /dev/null +++ b/goTool/go.mod @@ -0,0 +1,13 @@ +module powerpod/gotool + +go 1.26.2 + +require ( + go.bug.st/serial v1.6.4 + google.golang.org/protobuf v1.36.11 +) + +require ( + github.com/creack/goselect v0.1.2 // indirect + golang.org/x/sys v0.19.0 // indirect +) diff --git a/goTool/go.sum b/goTool/go.sum new file mode 100644 index 0000000..76e832c --- /dev/null +++ b/goTool/go.sum @@ -0,0 +1,18 @@ +github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= +github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= +go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/goTool/main.go b/goTool/main.go new file mode 100644 index 0000000..6d47584 --- /dev/null +++ b/goTool/main.go @@ -0,0 +1,90 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "time" + + "go.bug.st/serial" + "google.golang.org/protobuf/proto" + + "powerpod/gotool/pb" + uartframe "powerpod/gotool/uart" +) + +const ( + defaultBaud = 921600 + versionCmdID = byte(pb.MessageType_VERSION) + readTimeout = 3 * time.Second +) + +func main() { + portName := flag.String("port", "", "serial port (e.g. /dev/ttyUSB0)") + baud := flag.Int("baud", defaultBaud, "UART baud rate") + flag.Parse() + + if *portName == "" { + fmt.Fprintln(os.Stderr, "usage: gotool -port /dev/ttyUSB0") + flag.PrintDefaults() + os.Exit(2) + } + + mode := &serial.Mode{ + BaudRate: *baud, + DataBits: 8, + Parity: serial.NoParity, + StopBits: serial.OneStopBit, + } + + port, err := serial.Open(*portName, mode) + if err != nil { + log.Fatalf("open serial: %v", err) + } + defer port.Close() + + if err := port.SetReadTimeout(readTimeout); err != nil { + log.Fatalf("set read timeout: %v", err) + } + + frame, err := uartframe.EncodeFrame([]byte{versionCmdID}) + if err != nil { + log.Fatalf("encode frame: %v", err) + } + + log.Printf("sending VERSION command (%d bytes): % x", len(frame), frame) + if _, err := port.Write(frame); err != nil { + log.Fatalf("write: %v", err) + } + + payload, err := uartframe.ReadFrame(port, nil) + if err != nil { + log.Fatalf("read response: %v", err) + } + + log.Printf("response payload (%d bytes): % x", len(payload), payload) + if len(payload) == 0 { + log.Fatal("empty response payload") + } + if payload[0] != versionCmdID { + log.Fatalf("unexpected command id 0x%02x (want 0x%02x)", payload[0], versionCmdID) + } + + var msg pb.UartMessage + if err := proto.Unmarshal(payload[1:], &msg); err != nil { + log.Fatalf("decode protobuf: %v", err) + } + + if msg.GetType() != pb.MessageType_VERSION { + log.Fatalf("unexpected message type %v", msg.GetType()) + } + + ver := msg.GetVersionResponse() + if ver == nil { + log.Fatal("response missing version_response") + } + + fmt.Printf("version: %d\n", ver.GetVersion()) + fmt.Printf("git_hash: %s\n", ver.GetGitHash()) +} diff --git a/goTool/pb/uart_messages.pb.go b/goTool/pb/uart_messages.pb.go new file mode 100644 index 0000000..3708199 --- /dev/null +++ b/goTool/pb/uart_messages.pb.go @@ -0,0 +1,1035 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.34.1 +// source: uart_messages.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type MessageType int32 + +const ( + MessageType_UNKNOWN MessageType = 0 + MessageType_ACK MessageType = 1 + MessageType_ECHO MessageType = 2 + MessageType_VERSION MessageType = 3 + MessageType_CLIENT_INFO MessageType = 4 + MessageType_CLIENT_INPUT MessageType = 5 + MessageType_OTA_START MessageType = 16 + MessageType_OTA_PAYLOAD MessageType = 17 + MessageType_OTA_END MessageType = 18 + MessageType_OTA_STATUS MessageType = 19 + MessageType_OTA_START_ESPNOW MessageType = 20 +) + +// Enum value maps for MessageType. +var ( + MessageType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "ACK", + 2: "ECHO", + 3: "VERSION", + 4: "CLIENT_INFO", + 5: "CLIENT_INPUT", + 16: "OTA_START", + 17: "OTA_PAYLOAD", + 18: "OTA_END", + 19: "OTA_STATUS", + 20: "OTA_START_ESPNOW", + } + MessageType_value = map[string]int32{ + "UNKNOWN": 0, + "ACK": 1, + "ECHO": 2, + "VERSION": 3, + "CLIENT_INFO": 4, + "CLIENT_INPUT": 5, + "OTA_START": 16, + "OTA_PAYLOAD": 17, + "OTA_END": 18, + "OTA_STATUS": 19, + "OTA_START_ESPNOW": 20, + } +) + +func (x MessageType) Enum() *MessageType { + p := new(MessageType) + *p = x + return p +} + +func (x MessageType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MessageType) Descriptor() protoreflect.EnumDescriptor { + return file_uart_messages_proto_enumTypes[0].Descriptor() +} + +func (MessageType) Type() protoreflect.EnumType { + return &file_uart_messages_proto_enumTypes[0] +} + +func (x MessageType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MessageType.Descriptor instead. +func (MessageType) EnumDescriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{0} +} + +type UartMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type MessageType `protobuf:"varint,1,opt,name=type,proto3,enum=alox.MessageType" json:"type,omitempty"` + // Types that are valid to be assigned to Payload: + // + // *UartMessage_AckPayload + // *UartMessage_EchoPayload + // *UartMessage_VersionResponse + // *UartMessage_ClientInfoResponse + // *UartMessage_ClientInputResponse + // *UartMessage_OtaStart + // *UartMessage_OtaPayload + // *UartMessage_OtaEnd + // *UartMessage_OtaStatus + Payload isUartMessage_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UartMessage) Reset() { + *x = UartMessage{} + mi := &file_uart_messages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UartMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UartMessage) ProtoMessage() {} + +func (x *UartMessage) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[0] + 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 UartMessage.ProtoReflect.Descriptor instead. +func (*UartMessage) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{0} +} + +func (x *UartMessage) GetType() MessageType { + if x != nil { + return x.Type + } + return MessageType_UNKNOWN +} + +func (x *UartMessage) GetPayload() isUartMessage_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *UartMessage) GetAckPayload() *Ack { + if x != nil { + if x, ok := x.Payload.(*UartMessage_AckPayload); ok { + return x.AckPayload + } + } + return nil +} + +func (x *UartMessage) GetEchoPayload() *EchoPayload { + if x != nil { + if x, ok := x.Payload.(*UartMessage_EchoPayload); ok { + return x.EchoPayload + } + } + return nil +} + +func (x *UartMessage) GetVersionResponse() *VersionResponse { + if x != nil { + if x, ok := x.Payload.(*UartMessage_VersionResponse); ok { + return x.VersionResponse + } + } + return nil +} + +func (x *UartMessage) GetClientInfoResponse() *ClientInfoResponse { + if x != nil { + if x, ok := x.Payload.(*UartMessage_ClientInfoResponse); ok { + return x.ClientInfoResponse + } + } + return nil +} + +func (x *UartMessage) GetClientInputResponse() *ClientInputResponse { + if x != nil { + if x, ok := x.Payload.(*UartMessage_ClientInputResponse); ok { + return x.ClientInputResponse + } + } + return nil +} + +func (x *UartMessage) GetOtaStart() *OtaStartPayload { + if x != nil { + if x, ok := x.Payload.(*UartMessage_OtaStart); ok { + return x.OtaStart + } + } + return nil +} + +func (x *UartMessage) GetOtaPayload() *OtaPayload { + if x != nil { + if x, ok := x.Payload.(*UartMessage_OtaPayload); ok { + return x.OtaPayload + } + } + return nil +} + +func (x *UartMessage) GetOtaEnd() *OtaEndPayload { + if x != nil { + if x, ok := x.Payload.(*UartMessage_OtaEnd); ok { + return x.OtaEnd + } + } + return nil +} + +func (x *UartMessage) GetOtaStatus() *OtaStatusPayload { + if x != nil { + if x, ok := x.Payload.(*UartMessage_OtaStatus); ok { + return x.OtaStatus + } + } + return nil +} + +type isUartMessage_Payload interface { + isUartMessage_Payload() +} + +type UartMessage_AckPayload struct { + AckPayload *Ack `protobuf:"bytes,2,opt,name=ack_payload,json=ackPayload,proto3,oneof"` +} + +type UartMessage_EchoPayload struct { + EchoPayload *EchoPayload `protobuf:"bytes,3,opt,name=echo_payload,json=echoPayload,proto3,oneof"` +} + +type UartMessage_VersionResponse struct { + VersionResponse *VersionResponse `protobuf:"bytes,4,opt,name=version_response,json=versionResponse,proto3,oneof"` +} + +type UartMessage_ClientInfoResponse struct { + ClientInfoResponse *ClientInfoResponse `protobuf:"bytes,5,opt,name=client_info_response,json=clientInfoResponse,proto3,oneof"` +} + +type UartMessage_ClientInputResponse struct { + ClientInputResponse *ClientInputResponse `protobuf:"bytes,6,opt,name=client_input_response,json=clientInputResponse,proto3,oneof"` +} + +type UartMessage_OtaStart struct { + OtaStart *OtaStartPayload `protobuf:"bytes,7,opt,name=ota_start,json=otaStart,proto3,oneof"` +} + +type UartMessage_OtaPayload struct { + OtaPayload *OtaPayload `protobuf:"bytes,8,opt,name=ota_payload,json=otaPayload,proto3,oneof"` +} + +type UartMessage_OtaEnd struct { + OtaEnd *OtaEndPayload `protobuf:"bytes,9,opt,name=ota_end,json=otaEnd,proto3,oneof"` +} + +type UartMessage_OtaStatus struct { + OtaStatus *OtaStatusPayload `protobuf:"bytes,10,opt,name=ota_status,json=otaStatus,proto3,oneof"` +} + +func (*UartMessage_AckPayload) isUartMessage_Payload() {} + +func (*UartMessage_EchoPayload) isUartMessage_Payload() {} + +func (*UartMessage_VersionResponse) isUartMessage_Payload() {} + +func (*UartMessage_ClientInfoResponse) isUartMessage_Payload() {} + +func (*UartMessage_ClientInputResponse) isUartMessage_Payload() {} + +func (*UartMessage_OtaStart) isUartMessage_Payload() {} + +func (*UartMessage_OtaPayload) isUartMessage_Payload() {} + +func (*UartMessage_OtaEnd) isUartMessage_Payload() {} + +func (*UartMessage_OtaStatus) isUartMessage_Payload() {} + +type Ack struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Ack) Reset() { + *x = Ack{} + mi := &file_uart_messages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Ack) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ack) ProtoMessage() {} + +func (x *Ack) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_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 Ack.ProtoReflect.Descriptor instead. +func (*Ack) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{1} +} + +type EchoPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EchoPayload) Reset() { + *x = EchoPayload{} + mi := &file_uart_messages_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EchoPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EchoPayload) ProtoMessage() {} + +func (x *EchoPayload) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[2] + 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 EchoPayload.ProtoReflect.Descriptor instead. +func (*EchoPayload) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{2} +} + +func (x *EchoPayload) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type VersionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + GitHash string `protobuf:"bytes,2,opt,name=git_hash,json=gitHash,proto3" json:"git_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VersionResponse) Reset() { + *x = VersionResponse{} + mi := &file_uart_messages_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VersionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VersionResponse) ProtoMessage() {} + +func (x *VersionResponse) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[3] + 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 VersionResponse.ProtoReflect.Descriptor instead. +func (*VersionResponse) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{3} +} + +func (x *VersionResponse) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *VersionResponse) GetGitHash() string { + if x != nil { + return x.GitHash + } + return "" +} + +type ClientInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Available bool `protobuf:"varint,2,opt,name=available,proto3" json:"available,omitempty"` + Used bool `protobuf:"varint,3,opt,name=used,proto3" json:"used,omitempty"` + Mac []byte `protobuf:"bytes,4,opt,name=mac,proto3" json:"mac,omitempty"` + LastPing uint32 `protobuf:"varint,5,opt,name=last_ping,json=lastPing,proto3" json:"last_ping,omitempty"` + LastSuccessPing uint32 `protobuf:"varint,6,opt,name=last_success_ping,json=lastSuccessPing,proto3" json:"last_success_ping,omitempty"` + Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientInfo) Reset() { + *x = ClientInfo{} + mi := &file_uart_messages_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientInfo) ProtoMessage() {} + +func (x *ClientInfo) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_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 ClientInfo.ProtoReflect.Descriptor instead. +func (*ClientInfo) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{4} +} + +func (x *ClientInfo) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *ClientInfo) GetAvailable() bool { + if x != nil { + return x.Available + } + return false +} + +func (x *ClientInfo) GetUsed() bool { + if x != nil { + return x.Used + } + return false +} + +func (x *ClientInfo) GetMac() []byte { + if x != nil { + return x.Mac + } + return nil +} + +func (x *ClientInfo) GetLastPing() uint32 { + if x != nil { + return x.LastPing + } + return 0 +} + +func (x *ClientInfo) GetLastSuccessPing() uint32 { + if x != nil { + return x.LastSuccessPing + } + return 0 +} + +func (x *ClientInfo) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +type ClientInfoResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Clients []*ClientInfo `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientInfoResponse) Reset() { + *x = ClientInfoResponse{} + mi := &file_uart_messages_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientInfoResponse) ProtoMessage() {} + +func (x *ClientInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[5] + 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 ClientInfoResponse.ProtoReflect.Descriptor instead. +func (*ClientInfoResponse) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{5} +} + +func (x *ClientInfoResponse) GetClients() []*ClientInfo { + if x != nil { + return x.Clients + } + return nil +} + +type ClientInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + LageX float32 `protobuf:"fixed32,2,opt,name=lage_x,json=lageX,proto3" json:"lage_x,omitempty"` + LageY float32 `protobuf:"fixed32,3,opt,name=lage_y,json=lageY,proto3" json:"lage_y,omitempty"` + Bitmask uint32 `protobuf:"varint,4,opt,name=bitmask,proto3" json:"bitmask,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientInput) Reset() { + *x = ClientInput{} + mi := &file_uart_messages_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientInput) ProtoMessage() {} + +func (x *ClientInput) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[6] + 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 ClientInput.ProtoReflect.Descriptor instead. +func (*ClientInput) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{6} +} + +func (x *ClientInput) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *ClientInput) GetLageX() float32 { + if x != nil { + return x.LageX + } + return 0 +} + +func (x *ClientInput) GetLageY() float32 { + if x != nil { + return x.LageY + } + return 0 +} + +func (x *ClientInput) GetBitmask() uint32 { + if x != nil { + return x.Bitmask + } + return 0 +} + +type ClientInputResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Clients []*ClientInput `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientInputResponse) Reset() { + *x = ClientInputResponse{} + mi := &file_uart_messages_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientInputResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientInputResponse) ProtoMessage() {} + +func (x *ClientInputResponse) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[7] + 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 ClientInputResponse.ProtoReflect.Descriptor instead. +func (*ClientInputResponse) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{7} +} + +func (x *ClientInputResponse) GetClients() []*ClientInput { + if x != nil { + return x.Clients + } + return nil +} + +type OtaStartPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + TotalSize uint32 `protobuf:"varint,1,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` + BlockSize uint32 `protobuf:"varint,2,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OtaStartPayload) Reset() { + *x = OtaStartPayload{} + mi := &file_uart_messages_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OtaStartPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtaStartPayload) ProtoMessage() {} + +func (x *OtaStartPayload) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[8] + 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 OtaStartPayload.ProtoReflect.Descriptor instead. +func (*OtaStartPayload) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{8} +} + +func (x *OtaStartPayload) GetTotalSize() uint32 { + if x != nil { + return x.TotalSize + } + return 0 +} + +func (x *OtaStartPayload) GetBlockSize() uint32 { + if x != nil { + return x.BlockSize + } + return 0 +} + +type OtaPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + BlockId uint32 `protobuf:"varint,1,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` + ChunkId uint32 `protobuf:"varint,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OtaPayload) Reset() { + *x = OtaPayload{} + mi := &file_uart_messages_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OtaPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtaPayload) ProtoMessage() {} + +func (x *OtaPayload) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[9] + 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 OtaPayload.ProtoReflect.Descriptor instead. +func (*OtaPayload) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{9} +} + +func (x *OtaPayload) GetBlockId() uint32 { + if x != nil { + return x.BlockId + } + return 0 +} + +func (x *OtaPayload) GetChunkId() uint32 { + if x != nil { + return x.ChunkId + } + return 0 +} + +func (x *OtaPayload) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type OtaEndPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status uint32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OtaEndPayload) Reset() { + *x = OtaEndPayload{} + mi := &file_uart_messages_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OtaEndPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtaEndPayload) ProtoMessage() {} + +func (x *OtaEndPayload) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[10] + 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 OtaEndPayload.ProtoReflect.Descriptor instead. +func (*OtaEndPayload) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{10} +} + +func (x *OtaEndPayload) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +type OtaStatusPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status uint32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OtaStatusPayload) Reset() { + *x = OtaStatusPayload{} + mi := &file_uart_messages_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OtaStatusPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtaStatusPayload) ProtoMessage() {} + +func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[11] + 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 OtaStatusPayload.ProtoReflect.Descriptor instead. +func (*OtaStatusPayload) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{11} +} + +func (x *OtaStatusPayload) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +var File_uart_messages_proto protoreflect.FileDescriptor + +const file_uart_messages_proto_rawDesc = "" + + "\n" + + "\x13uart_messages.proto\x12\x04alox\"\xdc\x04\n" + + "\vUartMessage\x12%\n" + + "\x04type\x18\x01 \x01(\x0e2\x11.alox.MessageTypeR\x04type\x12,\n" + + "\vack_payload\x18\x02 \x01(\v2\t.alox.AckH\x00R\n" + + "ackPayload\x126\n" + + "\fecho_payload\x18\x03 \x01(\v2\x11.alox.EchoPayloadH\x00R\vechoPayload\x12B\n" + + "\x10version_response\x18\x04 \x01(\v2\x15.alox.VersionResponseH\x00R\x0fversionResponse\x12L\n" + + "\x14client_info_response\x18\x05 \x01(\v2\x18.alox.ClientInfoResponseH\x00R\x12clientInfoResponse\x12O\n" + + "\x15client_input_response\x18\x06 \x01(\v2\x19.alox.ClientInputResponseH\x00R\x13clientInputResponse\x124\n" + + "\tota_start\x18\a \x01(\v2\x15.alox.OtaStartPayloadH\x00R\botaStart\x123\n" + + "\vota_payload\x18\b \x01(\v2\x10.alox.OtaPayloadH\x00R\n" + + "otaPayload\x12.\n" + + "\aota_end\x18\t \x01(\v2\x13.alox.OtaEndPayloadH\x00R\x06otaEnd\x127\n" + + "\n" + + "ota_status\x18\n" + + " \x01(\v2\x16.alox.OtaStatusPayloadH\x00R\totaStatusB\t\n" + + "\apayload\"\x05\n" + + "\x03Ack\"!\n" + + "\vEchoPayload\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"F\n" + + "\x0fVersionResponse\x12\x18\n" + + "\aversion\x18\x01 \x01(\rR\aversion\x12\x19\n" + + "\bgit_hash\x18\x02 \x01(\tR\agitHash\"\xc3\x01\n" + + "\n" + + "ClientInfo\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x1c\n" + + "\tavailable\x18\x02 \x01(\bR\tavailable\x12\x12\n" + + "\x04used\x18\x03 \x01(\bR\x04used\x12\x10\n" + + "\x03mac\x18\x04 \x01(\fR\x03mac\x12\x1b\n" + + "\tlast_ping\x18\x05 \x01(\rR\blastPing\x12*\n" + + "\x11last_success_ping\x18\x06 \x01(\rR\x0flastSuccessPing\x12\x18\n" + + "\aversion\x18\a \x01(\rR\aversion\"@\n" + + "\x12ClientInfoResponse\x12*\n" + + "\aclients\x18\x01 \x03(\v2\x10.alox.ClientInfoR\aclients\"e\n" + + "\vClientInput\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x15\n" + + "\x06lage_x\x18\x02 \x01(\x02R\x05lageX\x12\x15\n" + + "\x06lage_y\x18\x03 \x01(\x02R\x05lageY\x12\x18\n" + + "\abitmask\x18\x04 \x01(\rR\abitmask\"B\n" + + "\x13ClientInputResponse\x12+\n" + + "\aclients\x18\x01 \x03(\v2\x11.alox.ClientInputR\aclients\"O\n" + + "\x0fOtaStartPayload\x12\x1d\n" + + "\n" + + "total_size\x18\x01 \x01(\rR\ttotalSize\x12\x1d\n" + + "\n" + + "block_size\x18\x02 \x01(\rR\tblockSize\"V\n" + + "\n" + + "OtaPayload\x12\x19\n" + + "\bblock_id\x18\x01 \x01(\rR\ablockId\x12\x19\n" + + "\bchunk_id\x18\x02 \x01(\rR\achunkId\x12\x12\n" + + "\x04data\x18\x03 \x01(\fR\x04data\"'\n" + + "\rOtaEndPayload\x12\x16\n" + + "\x06status\x18\x01 \x01(\rR\x06status\"*\n" + + "\x10OtaStatusPayload\x12\x16\n" + + "\x06status\x18\x01 \x01(\rR\x06status*\xb0\x01\n" + + "\vMessageType\x12\v\n" + + "\aUNKNOWN\x10\x00\x12\a\n" + + "\x03ACK\x10\x01\x12\b\n" + + "\x04ECHO\x10\x02\x12\v\n" + + "\aVERSION\x10\x03\x12\x0f\n" + + "\vCLIENT_INFO\x10\x04\x12\x10\n" + + "\fCLIENT_INPUT\x10\x05\x12\r\n" + + "\tOTA_START\x10\x10\x12\x0f\n" + + "\vOTA_PAYLOAD\x10\x11\x12\v\n" + + "\aOTA_END\x10\x12\x12\x0e\n" + + "\n" + + "OTA_STATUS\x10\x13\x12\x14\n" + + "\x10OTA_START_ESPNOW\x10\x14b\x06proto3" + +var ( + file_uart_messages_proto_rawDescOnce sync.Once + file_uart_messages_proto_rawDescData []byte +) + +func file_uart_messages_proto_rawDescGZIP() []byte { + file_uart_messages_proto_rawDescOnce.Do(func() { + file_uart_messages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_uart_messages_proto_rawDesc), len(file_uart_messages_proto_rawDesc))) + }) + return file_uart_messages_proto_rawDescData +} + +var file_uart_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_uart_messages_proto_goTypes = []any{ + (MessageType)(0), // 0: alox.MessageType + (*UartMessage)(nil), // 1: alox.UartMessage + (*Ack)(nil), // 2: alox.Ack + (*EchoPayload)(nil), // 3: alox.EchoPayload + (*VersionResponse)(nil), // 4: alox.VersionResponse + (*ClientInfo)(nil), // 5: alox.ClientInfo + (*ClientInfoResponse)(nil), // 6: alox.ClientInfoResponse + (*ClientInput)(nil), // 7: alox.ClientInput + (*ClientInputResponse)(nil), // 8: alox.ClientInputResponse + (*OtaStartPayload)(nil), // 9: alox.OtaStartPayload + (*OtaPayload)(nil), // 10: alox.OtaPayload + (*OtaEndPayload)(nil), // 11: alox.OtaEndPayload + (*OtaStatusPayload)(nil), // 12: alox.OtaStatusPayload +} +var file_uart_messages_proto_depIdxs = []int32{ + 0, // 0: alox.UartMessage.type:type_name -> alox.MessageType + 2, // 1: alox.UartMessage.ack_payload:type_name -> alox.Ack + 3, // 2: alox.UartMessage.echo_payload:type_name -> alox.EchoPayload + 4, // 3: alox.UartMessage.version_response:type_name -> alox.VersionResponse + 6, // 4: alox.UartMessage.client_info_response:type_name -> alox.ClientInfoResponse + 8, // 5: alox.UartMessage.client_input_response:type_name -> alox.ClientInputResponse + 9, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload + 10, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload + 11, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload + 12, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload + 5, // 10: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo + 7, // 11: alox.ClientInputResponse.clients:type_name -> alox.ClientInput + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_uart_messages_proto_init() } +func file_uart_messages_proto_init() { + if File_uart_messages_proto != nil { + return + } + file_uart_messages_proto_msgTypes[0].OneofWrappers = []any{ + (*UartMessage_AckPayload)(nil), + (*UartMessage_EchoPayload)(nil), + (*UartMessage_VersionResponse)(nil), + (*UartMessage_ClientInfoResponse)(nil), + (*UartMessage_ClientInputResponse)(nil), + (*UartMessage_OtaStart)(nil), + (*UartMessage_OtaPayload)(nil), + (*UartMessage_OtaEnd)(nil), + (*UartMessage_OtaStatus)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_uart_messages_proto_rawDesc), len(file_uart_messages_proto_rawDesc)), + NumEnums: 1, + NumMessages: 12, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_uart_messages_proto_goTypes, + DependencyIndexes: file_uart_messages_proto_depIdxs, + EnumInfos: file_uart_messages_proto_enumTypes, + MessageInfos: file_uart_messages_proto_msgTypes, + }.Build() + File_uart_messages_proto = out.File + file_uart_messages_proto_goTypes = nil + file_uart_messages_proto_depIdxs = nil +} diff --git a/goTool/uart/frame.go b/goTool/uart/frame.go new file mode 100644 index 0000000..8849e25 --- /dev/null +++ b/goTool/uart/frame.go @@ -0,0 +1,126 @@ +package uart + +import ( + "errors" + "fmt" + "io" +) + +const ( + StartMarker = 0xAA + StopMarker = 0xCC + MaxPayload = 252 +) + +var ( + ErrInvalidFrame = errors.New("invalid uart frame") + ErrTimeout = errors.New("read timeout") +) + +// EncodeFrame builds a framed packet: 0xAA len payload xor(payload) 0xCC. +func EncodeFrame(payload []byte) ([]byte, error) { + if len(payload) == 0 || len(payload) > MaxPayload { + return nil, fmt.Errorf("payload length %d out of range 1..%d", len(payload), MaxPayload) + } + + frame := make([]byte, 0, 4+len(payload)) + frame = append(frame, StartMarker, byte(len(payload))) + var checksum byte + for _, b := range payload { + frame = append(frame, b) + checksum ^= b + } + frame = append(frame, checksum, StopMarker) + return frame, nil +} + +type Parser struct { + state int + len int + payload []byte + index int + checksum byte +} + +const ( + stateStart = iota + stateLen + stateData + stateChecksum + stateStop +) + +func NewParser() *Parser { + return &Parser{state: stateStart} +} + +// Feed ingests one byte. ok is true when a complete frame is ready. +func (p *Parser) Feed(b byte) (payload []byte, ok bool, err error) { + switch p.state { + case stateStart: + if b == StartMarker { + p.index = 0 + p.checksum = 0 + p.state = stateLen + } + case stateLen: + if b == 0 || b > MaxPayload { + p.state = stateStart + } else { + p.len = int(b) + p.payload = make([]byte, p.len) + p.state = stateData + } + case stateData: + p.payload[p.index] = b + p.checksum ^= b + p.index++ + if p.index >= p.len { + p.state = stateChecksum + } + case stateChecksum: + if b == p.checksum { + p.state = stateStop + } else { + p.state = stateStart + return nil, false, ErrInvalidFrame + } + case stateStop: + p.state = stateStart + if b == StopMarker { + out := make([]byte, len(p.payload)) + copy(out, p.payload) + return out, true, nil + } + } + return nil, false, nil +} + +// ReadFrame reads bytes from r until one full frame is parsed or an error occurs. +func ReadFrame(r io.Reader, buf []byte) ([]byte, error) { + if buf == nil { + buf = make([]byte, 256) + } + parser := NewParser() + + for { + n, err := r.Read(buf) + if n > 0 { + for i := 0; i < n; i++ { + payload, ok, perr := parser.Feed(buf[i]) + if perr != nil { + return nil, perr + } + if ok { + return payload, nil + } + } + } + if err != nil { + if err == io.EOF { + return nil, ErrInvalidFrame + } + return nil, err + } + } +}