diff --git a/goTool/cmd_deadzone.go b/goTool/cmd_deadzone.go new file mode 100644 index 0000000..39d2007 --- /dev/null +++ b/goTool/cmd_deadzone.go @@ -0,0 +1,58 @@ +package main + +import ( + "flag" + "fmt" + + "google.golang.org/protobuf/proto" + + "powerpod/gotool/pb" +) + +func runDeadzone(sp *serialPort, args []string) error { + fs := flag.NewFlagSet("deadzone", flag.ExitOnError) + write := fs.Bool("set", false, "write deadzone (default: read)") + deadzone := fs.Uint("value", 100, "deadzone in raw accel LSB (with -set)") + clientID := fs.Uint("client", 0, "client id (0=local master BMA456)") + all := fs.Bool("all", false, "apply to all registered slaves (master only)") + if err := fs.Parse(args); err != nil { + return err + } + + req := &pb.AccelDeadzoneRequest{ + Write: *write, + Deadzone: uint32(*deadzone), + ClientId: uint32(*clientID), + AllClients: *all, + } + msg := &pb.UartMessage{ + Type: pb.MessageType_ACCEL_DEADZONE, + Payload: &pb.UartMessage_AccelDeadzoneRequest{ + AccelDeadzoneRequest: req, + }, + } + body, err := proto.Marshal(msg) + if err != nil { + return fmt.Errorf("encode request: %w", err) + } + + payload := append([]byte{byte(pb.MessageType_ACCEL_DEADZONE)}, body...) + respPayload, err := sp.exchangePayload(payload, "ACCEL_DEADZONE") + if err != nil { + return err + } + + var respMsg pb.UartMessage + if err := proto.Unmarshal(respPayload[1:], &respMsg); err != nil { + return fmt.Errorf("decode response: %w", err) + } + + r := respMsg.GetAccelDeadzoneResponse() + if r == nil { + return fmt.Errorf("response missing accel_deadzone_response") + } + + fmt.Printf("deadzone=%d client_id=%d success=%v slaves_updated=%d\n", + r.GetDeadzone(), r.GetClientId(), r.GetSuccess(), r.GetSlavesUpdated()) + return nil +} diff --git a/goTool/main.go b/goTool/main.go index b7f441b..038f0ea 100644 --- a/goTool/main.go +++ b/goTool/main.go @@ -13,7 +13,8 @@ func usage() { fmt.Fprintf(os.Stderr, "usage: gotool -port /dev/ttyUSB0 \n\n") fmt.Fprintf(os.Stderr, "commands:\n") fmt.Fprintf(os.Stderr, " version firmware version and git hash\n") - fmt.Fprintf(os.Stderr, " clients registered ESP-NOW slaves on the master\n\n") + fmt.Fprintf(os.Stderr, " clients registered ESP-NOW slaves on the master\n") + fmt.Fprintf(os.Stderr, " deadzone get/set accelerometer deadzone (LSB)\n\n") flag.PrintDefaults() } @@ -41,6 +42,8 @@ func main() { runErr = runVersion(sp) case "clients", "client-info": runErr = runClients(sp) + case "deadzone", "accel-deadzone": + runErr = runDeadzone(sp, flag.Args()[1:]) default: fmt.Fprintf(os.Stderr, "unknown command %q\n\n", cmd) usage() diff --git a/goTool/pb/uart_messages.pb.go b/goTool/pb/uart_messages.pb.go index 3708199..4e802e8 100644 --- a/goTool/pb/uart_messages.pb.go +++ b/goTool/pb/uart_messages.pb.go @@ -30,6 +30,7 @@ const ( MessageType_VERSION MessageType = 3 MessageType_CLIENT_INFO MessageType = 4 MessageType_CLIENT_INPUT MessageType = 5 + MessageType_ACCEL_DEADZONE MessageType = 6 MessageType_OTA_START MessageType = 16 MessageType_OTA_PAYLOAD MessageType = 17 MessageType_OTA_END MessageType = 18 @@ -46,6 +47,7 @@ var ( 3: "VERSION", 4: "CLIENT_INFO", 5: "CLIENT_INPUT", + 6: "ACCEL_DEADZONE", 16: "OTA_START", 17: "OTA_PAYLOAD", 18: "OTA_END", @@ -59,6 +61,7 @@ var ( "VERSION": 3, "CLIENT_INFO": 4, "CLIENT_INPUT": 5, + "ACCEL_DEADZONE": 6, "OTA_START": 16, "OTA_PAYLOAD": 17, "OTA_END": 18, @@ -108,6 +111,8 @@ type UartMessage struct { // *UartMessage_OtaPayload // *UartMessage_OtaEnd // *UartMessage_OtaStatus + // *UartMessage_AccelDeadzoneRequest + // *UartMessage_AccelDeadzoneResponse Payload isUartMessage_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -238,6 +243,24 @@ func (x *UartMessage) GetOtaStatus() *OtaStatusPayload { return nil } +func (x *UartMessage) GetAccelDeadzoneRequest() *AccelDeadzoneRequest { + if x != nil { + if x, ok := x.Payload.(*UartMessage_AccelDeadzoneRequest); ok { + return x.AccelDeadzoneRequest + } + } + return nil +} + +func (x *UartMessage) GetAccelDeadzoneResponse() *AccelDeadzoneResponse { + if x != nil { + if x, ok := x.Payload.(*UartMessage_AccelDeadzoneResponse); ok { + return x.AccelDeadzoneResponse + } + } + return nil +} + type isUartMessage_Payload interface { isUartMessage_Payload() } @@ -278,6 +301,14 @@ type UartMessage_OtaStatus struct { OtaStatus *OtaStatusPayload `protobuf:"bytes,10,opt,name=ota_status,json=otaStatus,proto3,oneof"` } +type UartMessage_AccelDeadzoneRequest struct { + AccelDeadzoneRequest *AccelDeadzoneRequest `protobuf:"bytes,11,opt,name=accel_deadzone_request,json=accelDeadzoneRequest,proto3,oneof"` +} + +type UartMessage_AccelDeadzoneResponse struct { + AccelDeadzoneResponse *AccelDeadzoneResponse `protobuf:"bytes,12,opt,name=accel_deadzone_response,json=accelDeadzoneResponse,proto3,oneof"` +} + func (*UartMessage_AckPayload) isUartMessage_Payload() {} func (*UartMessage_EchoPayload) isUartMessage_Payload() {} @@ -296,6 +327,10 @@ func (*UartMessage_OtaEnd) isUartMessage_Payload() {} func (*UartMessage_OtaStatus) isUartMessage_Payload() {} +func (*UartMessage_AccelDeadzoneRequest) isUartMessage_Payload() {} + +func (*UartMessage_AccelDeadzoneResponse) isUartMessage_Payload() {} + type Ack struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -676,6 +711,145 @@ func (x *ClientInputResponse) GetClients() []*ClientInput { return nil } +// write=false: read deadzone; write=true: apply deadzone (LSB per axis, raw accel units). +// client_id 0 = local BMA456 on this node; >0 = slave id on master; ignored on slave. +// all_clients = true (master only): push deadzone to every registered slave via ESP-NOW. +type AccelDeadzoneRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Write bool `protobuf:"varint,1,opt,name=write,proto3" json:"write,omitempty"` + Deadzone uint32 `protobuf:"varint,2,opt,name=deadzone,proto3" json:"deadzone,omitempty"` + ClientId uint32 `protobuf:"varint,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + AllClients bool `protobuf:"varint,4,opt,name=all_clients,json=allClients,proto3" json:"all_clients,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AccelDeadzoneRequest) Reset() { + *x = AccelDeadzoneRequest{} + mi := &file_uart_messages_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AccelDeadzoneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccelDeadzoneRequest) ProtoMessage() {} + +func (x *AccelDeadzoneRequest) 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 AccelDeadzoneRequest.ProtoReflect.Descriptor instead. +func (*AccelDeadzoneRequest) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{8} +} + +func (x *AccelDeadzoneRequest) GetWrite() bool { + if x != nil { + return x.Write + } + return false +} + +func (x *AccelDeadzoneRequest) GetDeadzone() uint32 { + if x != nil { + return x.Deadzone + } + return 0 +} + +func (x *AccelDeadzoneRequest) GetClientId() uint32 { + if x != nil { + return x.ClientId + } + return 0 +} + +func (x *AccelDeadzoneRequest) GetAllClients() bool { + if x != nil { + return x.AllClients + } + return false +} + +type AccelDeadzoneResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deadzone uint32 `protobuf:"varint,1,opt,name=deadzone,proto3" json:"deadzone,omitempty"` + ClientId uint32 `protobuf:"varint,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + Success bool `protobuf:"varint,3,opt,name=success,proto3" json:"success,omitempty"` + SlavesUpdated uint32 `protobuf:"varint,4,opt,name=slaves_updated,json=slavesUpdated,proto3" json:"slaves_updated,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AccelDeadzoneResponse) Reset() { + *x = AccelDeadzoneResponse{} + mi := &file_uart_messages_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AccelDeadzoneResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccelDeadzoneResponse) ProtoMessage() {} + +func (x *AccelDeadzoneResponse) 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 AccelDeadzoneResponse.ProtoReflect.Descriptor instead. +func (*AccelDeadzoneResponse) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{9} +} + +func (x *AccelDeadzoneResponse) GetDeadzone() uint32 { + if x != nil { + return x.Deadzone + } + return 0 +} + +func (x *AccelDeadzoneResponse) GetClientId() uint32 { + if x != nil { + return x.ClientId + } + return 0 +} + +func (x *AccelDeadzoneResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *AccelDeadzoneResponse) GetSlavesUpdated() uint32 { + if x != nil { + return x.SlavesUpdated + } + return 0 +} + 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"` @@ -686,7 +860,7 @@ type OtaStartPayload struct { func (x *OtaStartPayload) Reset() { *x = OtaStartPayload{} - mi := &file_uart_messages_proto_msgTypes[8] + mi := &file_uart_messages_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -698,7 +872,7 @@ func (x *OtaStartPayload) String() string { func (*OtaStartPayload) ProtoMessage() {} func (x *OtaStartPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[8] + mi := &file_uart_messages_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -711,7 +885,7 @@ func (x *OtaStartPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaStartPayload.ProtoReflect.Descriptor instead. func (*OtaStartPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{8} + return file_uart_messages_proto_rawDescGZIP(), []int{10} } func (x *OtaStartPayload) GetTotalSize() uint32 { @@ -739,7 +913,7 @@ type OtaPayload struct { func (x *OtaPayload) Reset() { *x = OtaPayload{} - mi := &file_uart_messages_proto_msgTypes[9] + mi := &file_uart_messages_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -751,7 +925,7 @@ func (x *OtaPayload) String() string { func (*OtaPayload) ProtoMessage() {} func (x *OtaPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[9] + mi := &file_uart_messages_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -764,7 +938,7 @@ func (x *OtaPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaPayload.ProtoReflect.Descriptor instead. func (*OtaPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{9} + return file_uart_messages_proto_rawDescGZIP(), []int{11} } func (x *OtaPayload) GetBlockId() uint32 { @@ -797,7 +971,7 @@ type OtaEndPayload struct { func (x *OtaEndPayload) Reset() { *x = OtaEndPayload{} - mi := &file_uart_messages_proto_msgTypes[10] + mi := &file_uart_messages_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -809,7 +983,7 @@ func (x *OtaEndPayload) String() string { func (*OtaEndPayload) ProtoMessage() {} func (x *OtaEndPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[10] + mi := &file_uart_messages_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -822,7 +996,7 @@ func (x *OtaEndPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaEndPayload.ProtoReflect.Descriptor instead. func (*OtaEndPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{10} + return file_uart_messages_proto_rawDescGZIP(), []int{12} } func (x *OtaEndPayload) GetStatus() uint32 { @@ -841,7 +1015,7 @@ type OtaStatusPayload struct { func (x *OtaStatusPayload) Reset() { *x = OtaStatusPayload{} - mi := &file_uart_messages_proto_msgTypes[11] + mi := &file_uart_messages_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -853,7 +1027,7 @@ func (x *OtaStatusPayload) String() string { func (*OtaStatusPayload) ProtoMessage() {} func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[11] + mi := &file_uart_messages_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -866,7 +1040,7 @@ func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use OtaStatusPayload.ProtoReflect.Descriptor instead. func (*OtaStatusPayload) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{11} + return file_uart_messages_proto_rawDescGZIP(), []int{13} } func (x *OtaStatusPayload) GetStatus() uint32 { @@ -880,7 +1054,7 @@ var File_uart_messages_proto protoreflect.FileDescriptor const file_uart_messages_proto_rawDesc = "" + "\n" + - "\x13uart_messages.proto\x12\x04alox\"\xdc\x04\n" + + "\x13uart_messages.proto\x12\x04alox\"\x87\x06\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" + @@ -895,7 +1069,9 @@ const file_uart_messages_proto_rawDesc = "" + "\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" + + " \x01(\v2\x16.alox.OtaStatusPayloadH\x00R\totaStatus\x12R\n" + + "\x16accel_deadzone_request\x18\v \x01(\v2\x1a.alox.AccelDeadzoneRequestH\x00R\x14accelDeadzoneRequest\x12U\n" + + "\x17accel_deadzone_response\x18\f \x01(\v2\x1b.alox.AccelDeadzoneResponseH\x00R\x15accelDeadzoneResponseB\t\n" + "\apayload\"\x05\n" + "\x03Ack\"!\n" + "\vEchoPayload\x12\x12\n" + @@ -920,7 +1096,18 @@ const file_uart_messages_proto_rawDesc = "" + "\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" + + "\aclients\x18\x01 \x03(\v2\x11.alox.ClientInputR\aclients\"\x86\x01\n" + + "\x14AccelDeadzoneRequest\x12\x14\n" + + "\x05write\x18\x01 \x01(\bR\x05write\x12\x1a\n" + + "\bdeadzone\x18\x02 \x01(\rR\bdeadzone\x12\x1b\n" + + "\tclient_id\x18\x03 \x01(\rR\bclientId\x12\x1f\n" + + "\vall_clients\x18\x04 \x01(\bR\n" + + "allClients\"\x91\x01\n" + + "\x15AccelDeadzoneResponse\x12\x1a\n" + + "\bdeadzone\x18\x01 \x01(\rR\bdeadzone\x12\x1b\n" + + "\tclient_id\x18\x02 \x01(\rR\bclientId\x12\x18\n" + + "\asuccess\x18\x03 \x01(\bR\asuccess\x12%\n" + + "\x0eslaves_updated\x18\x04 \x01(\rR\rslavesUpdated\"O\n" + "\x0fOtaStartPayload\x12\x1d\n" + "\n" + "total_size\x18\x01 \x01(\rR\ttotalSize\x12\x1d\n" + @@ -934,14 +1121,15 @@ const file_uart_messages_proto_rawDesc = "" + "\rOtaEndPayload\x12\x16\n" + "\x06status\x18\x01 \x01(\rR\x06status\"*\n" + "\x10OtaStatusPayload\x12\x16\n" + - "\x06status\x18\x01 \x01(\rR\x06status*\xb0\x01\n" + + "\x06status\x18\x01 \x01(\rR\x06status*\xc4\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" + + "\fCLIENT_INPUT\x10\x05\x12\x12\n" + + "\x0eACCEL_DEADZONE\x10\x06\x12\r\n" + "\tOTA_START\x10\x10\x12\x0f\n" + "\vOTA_PAYLOAD\x10\x11\x12\v\n" + "\aOTA_END\x10\x12\x12\x0e\n" + @@ -962,21 +1150,23 @@ func file_uart_messages_proto_rawDescGZIP() []byte { } 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_msgTypes = make([]protoimpl.MessageInfo, 14) 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 + (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 + (*AccelDeadzoneRequest)(nil), // 9: alox.AccelDeadzoneRequest + (*AccelDeadzoneResponse)(nil), // 10: alox.AccelDeadzoneResponse + (*OtaStartPayload)(nil), // 11: alox.OtaStartPayload + (*OtaPayload)(nil), // 12: alox.OtaPayload + (*OtaEndPayload)(nil), // 13: alox.OtaEndPayload + (*OtaStatusPayload)(nil), // 14: alox.OtaStatusPayload } var file_uart_messages_proto_depIdxs = []int32{ 0, // 0: alox.UartMessage.type:type_name -> alox.MessageType @@ -985,17 +1175,19 @@ var file_uart_messages_proto_depIdxs = []int32{ 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 + 11, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload + 12, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload + 13, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload + 14, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload + 9, // 10: alox.UartMessage.accel_deadzone_request:type_name -> alox.AccelDeadzoneRequest + 10, // 11: alox.UartMessage.accel_deadzone_response:type_name -> alox.AccelDeadzoneResponse + 5, // 12: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo + 7, // 13: alox.ClientInputResponse.clients:type_name -> alox.ClientInput + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_uart_messages_proto_init() } @@ -1013,6 +1205,8 @@ func file_uart_messages_proto_init() { (*UartMessage_OtaPayload)(nil), (*UartMessage_OtaEnd)(nil), (*UartMessage_OtaStatus)(nil), + (*UartMessage_AccelDeadzoneRequest)(nil), + (*UartMessage_AccelDeadzoneResponse)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1020,7 +1214,7 @@ func file_uart_messages_proto_init() { 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, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/goTool/serialport.go b/goTool/serialport.go index 2319fe2..96e82e6 100644 --- a/goTool/serialport.go +++ b/goTool/serialport.go @@ -38,6 +38,32 @@ func (s *serialPort) Close() error { return s.port.Close() } +func (s *serialPort) exchangePayload(payload []byte, cmdName string) ([]byte, error) { + if len(payload) == 0 { + return nil, fmt.Errorf("empty payload") + } + frame, err := uartframe.EncodeFrame(payload) + if err != nil { + return nil, fmt.Errorf("encode frame: %w", err) + } + + log.Printf("sending %s command (%d bytes): % x", cmdName, len(frame), frame) + if _, err := s.port.Write(frame); err != nil { + return nil, fmt.Errorf("write: %w", err) + } + + respPayload, err := uartframe.ReadFrame(s.port, nil) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + log.Printf("response payload (%d bytes): % x", len(respPayload), respPayload) + if len(respPayload) == 0 { + return nil, fmt.Errorf("empty response payload") + } + return respPayload, nil +} + func (s *serialPort) exchange(cmdID byte, cmdName string) ([]byte, error) { frame, err := uartframe.EncodeFrame([]byte{cmdID}) if err != nil { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 42bfedb..811dc80 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -16,6 +16,7 @@ idf_component_register( "cmd_handler.c" "cmd_version.c" "cmd_client_info.c" + "cmd_accel_deadzone.c" "client_registry.c" "esp_now_comm.c" "esp_now_proto.c" diff --git a/main/README.md b/main/README.md index 4967471..a9260ae 100644 --- a/main/README.md +++ b/main/README.md @@ -73,6 +73,7 @@ Schema: `proto/esp_now_messages.proto`. Encode/decode: `esp_now_proto.c`. The ES | `ESPNOW_DISCOVER` | Master → broadcast `FF:FF:FF:FF:FF:FF` | `EspNowDiscover` (`network`) | | `ESPNOW_SLAVE_INFO` | Slave → master | `EspNowSlavePresence` | | `ESPNOW_HEARTBEAT` | Slave → master | `EspNowSlavePresence` (same fields) | +| `ESPNOW_SET_ACCEL_DEADZONE` | Master → slave | `EspNowAccelDeadzone` (`deadzone` LSB) | `EspNowSlavePresence`: `network`, `mac` (6 bytes), `version`, `slave_id`, `available`, `used`. @@ -146,6 +147,7 @@ Host and master speak nanopb-encoded `UartMessage` inside UART frames (byte 0 = | 3 | `VERSION` | Implemented (`cmd_version.c`) | | 4 | `CLIENT_INFO` | Implemented (`cmd_client_info.c`) — slave list from registry | | 5 | `CLIENT_INPUT` | Planned | +| 6 | `ACCEL_DEADZONE` | Implemented (`cmd_accel_deadzone.c`) — get/set accel filter LSB | | 16–20 | OTA / ESP-NOW OTA | Planned | Regenerate C code: @@ -173,6 +175,21 @@ Build embeds `POWERPOD_GIT_HASH` via `git rev-parse` in `main/CMakeLists.txt`. Encoding: `uart_send_uart_message()` in `uart_proto.c`. +### ACCEL_DEADZONE command + +Filters BMA456 logs: a new accel line is emitted only when any axis changes by more than `deadzone` raw LSB since the last reported sample (default **100**). + +**Request:** framed `06` + nanopb `UartMessage` with `accel_deadzone_request`: + +| Field | Meaning | +|-------|---------| +| `write` | `false` = read, `true` = write | +| `deadzone` | Threshold in LSB (write) | +| `client_id` | `0` = local sensor on this node; `>0` = slave id (master) | +| `all_clients` | Master: ESP-NOW push to every registered slave | + +**Response:** `accel_deadzone_response` with applied `deadzone`, `success`, and `slaves_updated` (ESP-NOW count). + ### CLIENT_INFO command **Request:** framed payload `04` only (`MessageType.CLIENT_INFO`). diff --git a/main/bosch456.c b/main/bosch456.c index da78aa2..5c82fdf 100644 --- a/main/bosch456.c +++ b/main/bosch456.c @@ -18,6 +18,11 @@ static const char *TAG = "[BMA456]"; static i2c_master_dev_handle_t bma456_dev_handle; static bool s_bma456_ready; static struct bma4_dev bma456_struct; +static uint32_t s_accel_deadzone = BMA456_DEFAULT_ACCEL_DEADZONE; +static int16_t s_last_x; +static int16_t s_last_y; +static int16_t s_last_z; +static bool s_have_last_sample; volatile uint8_t interrupt_status = 0; uint8_t int_line; @@ -134,6 +139,49 @@ static esp_err_t check_bma4(const char *api_name, int8_t rslt) { return ESP_FAIL; } +static int16_t axis_delta(int16_t a, int16_t b) { + int32_t d = (int32_t)a - (int32_t)b; + return (int16_t)(d < 0 ? -d : d); +} + +static bool sample_exceeds_deadzone(int16_t x, int16_t y, int16_t z) { + if (!s_have_last_sample) { + return true; + } + return axis_delta(x, s_last_x) > (int16_t)s_accel_deadzone || + axis_delta(y, s_last_y) > (int16_t)s_accel_deadzone || + axis_delta(z, s_last_z) > (int16_t)s_accel_deadzone; +} + +bool bma456_is_ready(void) { return s_bma456_ready; } + +void bma456_set_accel_deadzone(uint32_t deadzone_lsb) { + s_accel_deadzone = deadzone_lsb; + s_have_last_sample = false; + if (s_bma456_ready) { + ESP_LOGI(TAG, "accel deadzone applied: %lu LSB", (unsigned long)deadzone_lsb); + } +} + +uint32_t bma456_get_accel_deadzone(void) { return s_accel_deadzone; } + +void bma456_report_accel_if_changed(int16_t x, int16_t y, int16_t z) { + if (!s_bma456_ready) { + return; + } + if (!sample_exceeds_deadzone(x, y, z)) { + return; + } + + s_last_x = x; + s_last_y = y; + s_last_z = z; + s_have_last_sample = true; + + ESP_LOGI(TAG, "ACC X=%d Y=%d Z=%d (deadzone %lu)", x, y, z, + (unsigned long)s_accel_deadzone); +} + static void remove_bma456_device(void) { if (bma456_dev_handle != NULL) { i2c_master_bus_rm_device(bma456_dev_handle); @@ -153,10 +201,11 @@ void read_sensor_task(void *params) { while (1) { ret = bma4_read_accel_xyz(&sens_data, &bma456_struct); - bma4_error_codes_print_result("bma4_read_accel_xyz", ret); - - ESP_LOGI("ACC", "X: %d, Y: %d, Z: %d", sens_data.x, sens_data.y, - sens_data.z); + if (ret == BMA4_OK) { + bma456_report_accel_if_changed(sens_data.x, sens_data.y, sens_data.z); + } else { + bma4_error_codes_print_result("bma4_read_accel_xyz", ret); + } if (interrupt_status) { ESP_LOGI("INTERRUPT", "Da war der Interrupt resetting"); diff --git a/main/bosch456.h b/main/bosch456.h index 17f6a4b..a05cbc6 100644 --- a/main/bosch456.h +++ b/main/bosch456.h @@ -9,7 +9,17 @@ #define BMA456_ADDRESS 0x18 +#define BMA456_DEFAULT_ACCEL_DEADZONE 100u + /** Initialize BMA456 on the shared I2C bus. Returns ESP_OK or logs and skips sensor use. */ esp_err_t init_bma456(i2c_master_bus_handle_t bus_handle); +bool bma456_is_ready(void); + +void bma456_set_accel_deadzone(uint32_t deadzone_lsb); +uint32_t bma456_get_accel_deadzone(void); + +/** Log accel sample only when any axis changed more than deadzone since last report. */ +void bma456_report_accel_if_changed(int16_t x, int16_t y, int16_t z); + #endif diff --git a/main/client_registry.c b/main/client_registry.c index 4514015..f913c2a 100644 --- a/main/client_registry.c +++ b/main/client_registry.c @@ -60,6 +60,7 @@ static client_slot_t *alloc_slot(const uint8_t mac[CLIENT_MAC_LEN], slot = &s_clients[i]; slot->active = true; memcpy(slot->info.mac, mac, CLIENT_MAC_LEN); + slot->info.accel_deadzone = CLIENT_REGISTRY_DEFAULT_ACCEL_DEADZONE; if (out_is_new != NULL) { *out_is_new = true; } @@ -169,6 +170,59 @@ size_t client_registry_count(void) { return n; } +const client_info_t *client_registry_find_by_id(uint32_t id) { + for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) { + if (s_clients[i].active && s_clients[i].info.id == id) { + return &s_clients[i].info; + } + } + return NULL; +} + +esp_err_t client_registry_set_accel_deadzone(uint32_t client_id, + uint32_t deadzone) { + const client_info_t *info = client_registry_find_by_id(client_id); + if (info == NULL) { + return ESP_ERR_NOT_FOUND; + } + + for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) { + if (s_clients[i].active && s_clients[i].info.id == client_id) { + s_clients[i].info.accel_deadzone = deadzone; + return ESP_OK; + } + } + return ESP_ERR_NOT_FOUND; +} + +esp_err_t client_registry_get_accel_deadzone(uint32_t client_id, + uint32_t *deadzone_out) { + if (deadzone_out == NULL) { + return ESP_ERR_INVALID_ARG; + } + + const client_info_t *info = client_registry_find_by_id(client_id); + if (info == NULL) { + return ESP_ERR_NOT_FOUND; + } + + *deadzone_out = info->accel_deadzone; + return ESP_OK; +} + +size_t client_registry_set_accel_deadzone_all(uint32_t deadzone) { + size_t n = 0; + + for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) { + if (!s_clients[i].active) { + continue; + } + s_clients[i].info.accel_deadzone = deadzone; + n++; + } + return n; +} + const client_info_t *client_registry_at(size_t index) { size_t n = 0; for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) { diff --git a/main/client_registry.h b/main/client_registry.h index 1ef6e8e..49c488f 100644 --- a/main/client_registry.h +++ b/main/client_registry.h @@ -19,8 +19,12 @@ typedef struct { /** Milliseconds since boot when last heartbeat / SLAVE_INFO was accepted. */ uint32_t last_success_ping_at; uint32_t version; + /** Accel deadzone in raw LSB per axis (master copy for ESP-NOW config). */ + uint32_t accel_deadzone; } client_info_t; +#define CLIENT_REGISTRY_DEFAULT_ACCEL_DEADZONE 100u + void client_registry_init(void); /** Milliseconds since boot (same clock as stored ping timestamps). */ @@ -49,5 +53,14 @@ void client_registry_check_timeouts(uint32_t timeout_ms); size_t client_registry_count(void); const client_info_t *client_registry_at(size_t index); const client_info_t *client_registry_find_by_mac(const uint8_t mac[CLIENT_MAC_LEN]); +const client_info_t *client_registry_find_by_id(uint32_t id); + +esp_err_t client_registry_set_accel_deadzone(uint32_t client_id, + uint32_t deadzone); +esp_err_t client_registry_get_accel_deadzone(uint32_t client_id, + uint32_t *deadzone_out); + +/** Push deadzone to all active registry entries; returns count updated. */ +size_t client_registry_set_accel_deadzone_all(uint32_t deadzone); #endif diff --git a/main/cmd_accel_deadzone.c b/main/cmd_accel_deadzone.c new file mode 100644 index 0000000..20ecd51 --- /dev/null +++ b/main/cmd_accel_deadzone.c @@ -0,0 +1,124 @@ +#include "bosch456.h" +#include "client_registry.h" +#include "cmd_accel_deadzone.h" +#include "cmd_handler.h" +#include "esp_log.h" +#include "esp_now_comm.h" +#include "pb_decode.h" +#include "uart_messages.pb.h" +#include "uart_proto.h" +#include + +static const char *TAG = "[ACCEL_DZ]"; + +static void send_response(uint32_t deadzone, uint32_t client_id, bool success, + uint32_t slaves_updated) { + alox_UartMessage response = alox_UartMessage_init_zero; + response.type = alox_MessageType_ACCEL_DEADZONE; + response.which_payload = alox_UartMessage_accel_deadzone_response_tag; + response.payload.accel_deadzone_response.deadzone = deadzone; + response.payload.accel_deadzone_response.client_id = client_id; + response.payload.accel_deadzone_response.success = success; + response.payload.accel_deadzone_response.slaves_updated = slaves_updated; + + if (uart_send_uart_message(&response) != ESP_OK) { + ESP_LOGE(TAG, "failed to send response"); + } +} + +static esp_err_t push_deadzone_to_slave(const client_info_t *client, + uint32_t deadzone) { + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = + client_registry_set_accel_deadzone(client->id, deadzone); + if (err != ESP_OK) { + return err; + } + + return esp_now_comm_send_accel_deadzone(client->id, deadzone); +} + +static void handle_accel_deadzone(const uint8_t *data, size_t len) { + alox_UartMessage uart_msg = alox_UartMessage_init_zero; + alox_AccelDeadzoneRequest req = alox_AccelDeadzoneRequest_init_zero; + bool have_request = false; + + if (len > 0) { + pb_istream_t stream = pb_istream_from_buffer(data, len); + if (!pb_decode(&stream, alox_UartMessage_fields, &uart_msg)) { + ESP_LOGW(TAG, "decode failed"); + send_response(BMA456_DEFAULT_ACCEL_DEADZONE, 0, false, 0); + return; + } + if (uart_msg.which_payload == + alox_UartMessage_accel_deadzone_request_tag) { + req = uart_msg.payload.accel_deadzone_request; + have_request = true; + } + } + + if (!have_request) { + req.write = false; + req.client_id = 0; + req.all_clients = false; + } + + if (req.write) { + if (req.all_clients) { + size_t n = client_registry_set_accel_deadzone_all(req.deadzone); + esp_err_t send_err = esp_now_comm_send_accel_deadzone(0, req.deadzone); + + if (bma456_is_ready()) { + bma456_set_accel_deadzone(req.deadzone); + } + + ESP_LOGI(TAG, "set deadzone %lu broadcast (registry %u)", + (unsigned long)req.deadzone, (unsigned)n); + send_response(req.deadzone, 0, send_err == ESP_OK || bma456_is_ready(), + send_err == ESP_OK ? (uint32_t)n : 0); + return; + } + + if (req.client_id == 0) { + bma456_set_accel_deadzone(req.deadzone); + ESP_LOGI(TAG, "set local deadzone %lu (no ESP-NOW; use -client or -all " + "for slaves)", + (unsigned long)req.deadzone); + send_response(req.deadzone, 0, true, 0); + return; + } + + const client_info_t *client = client_registry_find_by_id(req.client_id); + if (client == NULL) { + ESP_LOGW(TAG, "client id %lu not found", + (unsigned long)req.client_id); + send_response(req.deadzone, req.client_id, false, 0); + return; + } + + esp_err_t err = push_deadzone_to_slave(client, req.deadzone); + send_response(req.deadzone, req.client_id, err == ESP_OK, err == ESP_OK); + return; + } + + /* Read */ + if (req.all_clients || req.client_id == 0) { + uint32_t dz = bma456_get_accel_deadzone(); + send_response(dz, 0, true, 0); + return; + } + + uint32_t dz = 0; + esp_err_t err = client_registry_get_accel_deadzone(req.client_id, &dz); + send_response(dz, req.client_id, err == ESP_OK, 0); +} + +void cmd_accel_deadzone_register(void) { + if (msg_register_handler(alox_MessageType_ACCEL_DEADZONE, + handle_accel_deadzone) != ESP_OK) { + ESP_LOGE(TAG, "register failed"); + } +} diff --git a/main/cmd_accel_deadzone.h b/main/cmd_accel_deadzone.h new file mode 100644 index 0000000..3ad4d16 --- /dev/null +++ b/main/cmd_accel_deadzone.h @@ -0,0 +1,6 @@ +#ifndef CMD_ACCEL_DEADZONE_H +#define CMD_ACCEL_DEADZONE_H + +void cmd_accel_deadzone_register(void); + +#endif diff --git a/main/cmd_handler.c b/main/cmd_handler.c index 09c2447..03bacb9 100644 --- a/main/cmd_handler.c +++ b/main/cmd_handler.c @@ -26,6 +26,8 @@ static const char *message_type_name(uint16_t id) { return "CLIENT_INFO"; case alox_MessageType_CLIENT_INPUT: return "CLIENT_INPUT"; + case alox_MessageType_ACCEL_DEADZONE: + return "ACCEL_DEADZONE"; case alox_MessageType_OTA_START: return "OTA_START"; case alox_MessageType_OTA_PAYLOAD: diff --git a/main/esp_now_comm.c b/main/esp_now_comm.c index b3ec608..5c2d1bf 100644 --- a/main/esp_now_comm.c +++ b/main/esp_now_comm.c @@ -1,3 +1,4 @@ +#include "bosch456.h" #include "client_registry.h" #include "esp_now_comm.h" #include "esp_now_proto.h" @@ -109,6 +110,35 @@ static esp_err_t send_message(const uint8_t *dest_mac, return err; } +static esp_err_t send_accel_deadzone(uint32_t client_id, uint32_t deadzone) { + alox_EspNowMessage msg = alox_EspNowMessage_init_zero; + + msg.type = alox_EspNowMessageType_ESPNOW_SET_ACCEL_DEADZONE; + msg.which_payload = alox_EspNowMessage_accel_deadzone_tag; + msg.payload.accel_deadzone.deadzone = deadzone; + msg.payload.accel_deadzone.client_id = client_id; + + return send_message(ESPNOW_BCAST, &msg); +} + +esp_err_t esp_now_comm_send_accel_deadzone(uint32_t client_id, uint32_t deadzone) { + if (!s_config.master) { + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err = send_accel_deadzone(client_id, deadzone); + if (err == ESP_OK) { + ESP_LOGI(TAG, + "broadcast SET_ACCEL_DEADZONE deadzone=%lu client_id=%lu%s", + (unsigned long)deadzone, (unsigned long)client_id, + client_id == 0 ? " (all slaves)" : ""); + } else { + ESP_LOGW(TAG, "broadcast SET_ACCEL_DEADZONE failed: %s", + esp_err_to_name(err)); + } + return err; +} + static void send_presence(const uint8_t *dest_mac, alox_EspNowMessageType type) { alox_EspNowMessage msg = alox_EspNowMessage_init_zero; @@ -133,6 +163,29 @@ static void slave_reset_join(void) { s_last_discover_ms = 0; } +static void handle_slave_accel_deadzone(const uint8_t *master_mac, + const alox_EspNowAccelDeadzone *cfg) { + uint32_t my_id = s_own_mac[5]; + + if (cfg->client_id != 0 && cfg->client_id != my_id) { + return; + } + + if (s_slave_joined && !mac_equal(master_mac, s_master_mac)) { + return; + } + + char mac_str[18]; + mac_to_str(master_mac, mac_str, sizeof(mac_str)); + + ESP_LOGI(TAG, + "accel deadzone from master %s: %lu LSB id=%lu (sensor %s)", + mac_str, (unsigned long)cfg->deadzone, (unsigned long)my_id, + bma456_is_ready() ? "ok" : "not installed"); + + bma456_set_accel_deadzone(cfg->deadzone); +} + static void handle_client_presence(const alox_EspNowSlavePresence *presence, const uint8_t mac[CLIENT_MAC_LEN]) { if (presence->network != s_config.network) { @@ -184,6 +237,7 @@ static void handle_discover(const uint8_t *sender_mac, memcpy(s_master_mac, sender_mac, ESP_NOW_ETH_ALEN); s_slave_joined = true; s_last_discover_ms = now; + ensure_peer(sender_mac); char mac_str[18]; mac_to_str(sender_mac, mac_str, sizeof(mac_str)); @@ -247,34 +301,42 @@ static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data, return; } - alox_EspNowMessage msg = alox_EspNowMessage_init_zero; - uint8_t peer_mac[CLIENT_MAC_LEN]; + if (!s_config.master) { + alox_EspNowMessage msg = alox_EspNowMessage_init_zero; - esp_now_proto_setup_message_decode(&msg, peer_mac); - if (esp_now_proto_decode(data, (size_t)len, &msg) != ESP_OK) { + if (esp_now_proto_decode(data, (size_t)len, &msg) != ESP_OK) { + ESP_LOGW(TAG, "slave: ESP-NOW decode failed (%d bytes)", len); + return; + } + + switch (msg.which_payload) { + case alox_EspNowMessage_discover_tag: + handle_discover(info->src_addr, &msg.payload.discover); + break; + case alox_EspNowMessage_accel_deadzone_tag: + handle_slave_accel_deadzone(info->src_addr, &msg.payload.accel_deadzone); + break; + default: + ESP_LOGW(TAG, "slave: unhandled ESP-NOW which=%u type=%u", msg.which_payload, + (unsigned)msg.type); + break; + } return; } - if (!s_config.master) { - if (msg.type != alox_EspNowMessageType_ESPNOW_DISCOVER || - msg.which_payload != alox_EspNowMessage_discover_tag) { - return; - } - handle_discover(info->src_addr, &msg.payload.discover); + alox_EspNowMessage msg = alox_EspNowMessage_init_zero; + uint8_t peer_mac[CLIENT_MAC_LEN]; + + if (esp_now_proto_decode_with_mac(data, (size_t)len, &msg, peer_mac) != + ESP_OK) { + ESP_LOGW(TAG, "master: ESP-NOW decode failed (%d bytes)", len); return; } const alox_EspNowSlavePresence *presence = esp_now_proto_get_presence(&msg); - if (presence == NULL) { - return; + if (presence != NULL) { + handle_client_presence(presence, peer_mac); } - - if (msg.type != alox_EspNowMessageType_ESPNOW_SLAVE_INFO && - msg.type != alox_EspNowMessageType_ESPNOW_HEARTBEAT) { - return; - } - - handle_client_presence(presence, peer_mac); } static void master_discover_task(void *param) { diff --git a/main/esp_now_comm.h b/main/esp_now_comm.h index adafe7a..1d0a16e 100644 --- a/main/esp_now_comm.h +++ b/main/esp_now_comm.h @@ -2,8 +2,15 @@ #define ESP_NOW_COMM_H #include "app_config.h" +#include "client_registry.h" #include "esp_err.h" esp_err_t esp_now_comm_init(const app_config_t *config); +/** + * Master: broadcast accel deadzone (same path as DISCOVER). + * client_id 0 = all slaves; otherwise only the slave with that id applies it. + */ +esp_err_t esp_now_comm_send_accel_deadzone(uint32_t client_id, uint32_t deadzone); + #endif diff --git a/main/esp_now_proto.c b/main/esp_now_proto.c index 53940aa..977711f 100644 --- a/main/esp_now_proto.c +++ b/main/esp_now_proto.c @@ -77,6 +77,16 @@ esp_err_t esp_now_proto_decode(const uint8_t *data, size_t len, return ESP_OK; } +esp_err_t esp_now_proto_decode_with_mac(const uint8_t *data, size_t len, + alox_EspNowMessage *msg, + uint8_t mac_out[6]) { + if (mac_out == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_now_proto_setup_message_decode(msg, mac_out); + return esp_now_proto_decode(data, len, msg); +} + const alox_EspNowSlavePresence * esp_now_proto_get_presence(const alox_EspNowMessage *msg) { if (msg == NULL) { diff --git a/main/esp_now_proto.h b/main/esp_now_proto.h index 1f4da48..2dff491 100644 --- a/main/esp_now_proto.h +++ b/main/esp_now_proto.h @@ -14,6 +14,11 @@ esp_err_t esp_now_proto_encode(const alox_EspNowMessage *msg, uint8_t *buf, esp_err_t esp_now_proto_decode(const uint8_t *data, size_t len, alox_EspNowMessage *msg); +/** Decode messages that carry EspNowSlavePresence (mac callback required). */ +esp_err_t esp_now_proto_decode_with_mac(const uint8_t *data, size_t len, + alox_EspNowMessage *msg, + uint8_t mac_out[6]); + void esp_now_proto_setup_presence_encode(alox_EspNowSlavePresence *presence, const uint8_t mac[6]); diff --git a/main/powerpod.c b/main/powerpod.c index cdcdede..e293de2 100644 --- a/main/powerpod.c +++ b/main/powerpod.c @@ -1,5 +1,6 @@ #include "app_config.h" #include "cmd_handler.h" +#include "cmd_accel_deadzone.h" #include "cmd_client_info.h" #include "cmd_version.h" #include "esp_now_comm.h" @@ -144,6 +145,7 @@ void app_main(void) { init_uart(cmd_queue); cmd_version_register(); cmd_client_info_register(); + cmd_accel_deadzone_register(); } uint8_t current_digit = 10; diff --git a/main/proto/esp_now_messages.pb.c b/main/proto/esp_now_messages.pb.c index 939f833..eb63831 100644 --- a/main/proto/esp_now_messages.pb.c +++ b/main/proto/esp_now_messages.pb.c @@ -12,6 +12,9 @@ PB_BIND(alox_EspNowDiscover, alox_EspNowDiscover, AUTO) PB_BIND(alox_EspNowSlavePresence, alox_EspNowSlavePresence, AUTO) +PB_BIND(alox_EspNowAccelDeadzone, alox_EspNowAccelDeadzone, AUTO) + + PB_BIND(alox_EspNowMessage, alox_EspNowMessage, AUTO) diff --git a/main/proto/esp_now_messages.pb.h b/main/proto/esp_now_messages.pb.h index 2eb3257..547edd6 100644 --- a/main/proto/esp_now_messages.pb.h +++ b/main/proto/esp_now_messages.pb.h @@ -14,7 +14,8 @@ typedef enum _alox_EspNowMessageType { alox_EspNowMessageType_ESPNOW_UNKNOWN = 0, alox_EspNowMessageType_ESPNOW_DISCOVER = 1, alox_EspNowMessageType_ESPNOW_SLAVE_INFO = 2, - alox_EspNowMessageType_ESPNOW_HEARTBEAT = 3 + alox_EspNowMessageType_ESPNOW_HEARTBEAT = 3, + alox_EspNowMessageType_ESPNOW_SET_ACCEL_DEADZONE = 4 } alox_EspNowMessageType; /* Struct definitions */ @@ -31,6 +32,11 @@ typedef struct _alox_EspNowSlavePresence { bool used; } alox_EspNowSlavePresence; +typedef struct _alox_EspNowAccelDeadzone { + uint32_t deadzone; + uint32_t client_id; /* 0 = all slaves; otherwise only matching slave_id applies */ +} alox_EspNowAccelDeadzone; + typedef struct _alox_EspNowMessage { alox_EspNowMessageType type; pb_size_t which_payload; @@ -38,6 +44,7 @@ typedef struct _alox_EspNowMessage { alox_EspNowDiscover discover; alox_EspNowSlavePresence slave_info; alox_EspNowSlavePresence heartbeat; + alox_EspNowAccelDeadzone accel_deadzone; } payload; } alox_EspNowMessage; @@ -48,8 +55,9 @@ extern "C" { /* Helper constants for enums */ #define _alox_EspNowMessageType_MIN alox_EspNowMessageType_ESPNOW_UNKNOWN -#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_HEARTBEAT -#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_HEARTBEAT+1)) +#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_SET_ACCEL_DEADZONE +#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_SET_ACCEL_DEADZONE+1)) + @@ -59,9 +67,11 @@ extern "C" { /* Initializer values for message structs */ #define alox_EspNowDiscover_init_default {0} #define alox_EspNowSlavePresence_init_default {0, {{NULL}, NULL}, 0, 0, 0, 0} +#define alox_EspNowAccelDeadzone_init_default {0, 0} #define alox_EspNowMessage_init_default {_alox_EspNowMessageType_MIN, 0, {alox_EspNowDiscover_init_default}} #define alox_EspNowDiscover_init_zero {0} #define alox_EspNowSlavePresence_init_zero {0, {{NULL}, NULL}, 0, 0, 0, 0} +#define alox_EspNowAccelDeadzone_init_zero {0, 0} #define alox_EspNowMessage_init_zero {_alox_EspNowMessageType_MIN, 0, {alox_EspNowDiscover_init_zero}} /* Field tags (for use in manual encoding/decoding) */ @@ -72,10 +82,13 @@ extern "C" { #define alox_EspNowSlavePresence_slave_id_tag 4 #define alox_EspNowSlavePresence_available_tag 5 #define alox_EspNowSlavePresence_used_tag 6 +#define alox_EspNowAccelDeadzone_deadzone_tag 1 +#define alox_EspNowAccelDeadzone_client_id_tag 2 #define alox_EspNowMessage_type_tag 1 #define alox_EspNowMessage_discover_tag 2 #define alox_EspNowMessage_slave_info_tag 3 #define alox_EspNowMessage_heartbeat_tag 4 +#define alox_EspNowMessage_accel_deadzone_tag 5 /* Struct field encoding specification for nanopb */ #define alox_EspNowDiscover_FIELDLIST(X, a) \ @@ -93,30 +106,41 @@ X(a, STATIC, SINGULAR, BOOL, used, 6) #define alox_EspNowSlavePresence_CALLBACK pb_default_field_callback #define alox_EspNowSlavePresence_DEFAULT NULL +#define alox_EspNowAccelDeadzone_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, deadzone, 1) \ +X(a, STATIC, SINGULAR, UINT32, client_id, 2) +#define alox_EspNowAccelDeadzone_CALLBACK NULL +#define alox_EspNowAccelDeadzone_DEFAULT NULL + #define alox_EspNowMessage_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, type, 1) \ X(a, STATIC, ONEOF, MESSAGE, (payload,discover,payload.discover), 2) \ X(a, STATIC, ONEOF, MESSAGE, (payload,slave_info,payload.slave_info), 3) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,heartbeat,payload.heartbeat), 4) +X(a, STATIC, ONEOF, MESSAGE, (payload,heartbeat,payload.heartbeat), 4) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,accel_deadzone,payload.accel_deadzone), 5) #define alox_EspNowMessage_CALLBACK NULL #define alox_EspNowMessage_DEFAULT NULL #define alox_EspNowMessage_payload_discover_MSGTYPE alox_EspNowDiscover #define alox_EspNowMessage_payload_slave_info_MSGTYPE alox_EspNowSlavePresence #define alox_EspNowMessage_payload_heartbeat_MSGTYPE alox_EspNowSlavePresence +#define alox_EspNowMessage_payload_accel_deadzone_MSGTYPE alox_EspNowAccelDeadzone extern const pb_msgdesc_t alox_EspNowDiscover_msg; extern const pb_msgdesc_t alox_EspNowSlavePresence_msg; +extern const pb_msgdesc_t alox_EspNowAccelDeadzone_msg; extern const pb_msgdesc_t alox_EspNowMessage_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define alox_EspNowDiscover_fields &alox_EspNowDiscover_msg #define alox_EspNowSlavePresence_fields &alox_EspNowSlavePresence_msg +#define alox_EspNowAccelDeadzone_fields &alox_EspNowAccelDeadzone_msg #define alox_EspNowMessage_fields &alox_EspNowMessage_msg /* Maximum encoded size of messages (where known) */ /* alox_EspNowSlavePresence_size depends on runtime parameters */ /* alox_EspNowMessage_size depends on runtime parameters */ -#define ALOX_MAIN_PROTO_ESP_NOW_MESSAGES_PB_H_MAX_SIZE alox_EspNowDiscover_size +#define ALOX_MAIN_PROTO_ESP_NOW_MESSAGES_PB_H_MAX_SIZE alox_EspNowAccelDeadzone_size +#define alox_EspNowAccelDeadzone_size 12 #define alox_EspNowDiscover_size 6 #ifdef __cplusplus diff --git a/main/proto/esp_now_messages.proto b/main/proto/esp_now_messages.proto index cc0019d..f07421a 100644 --- a/main/proto/esp_now_messages.proto +++ b/main/proto/esp_now_messages.proto @@ -7,6 +7,7 @@ enum EspNowMessageType { ESPNOW_DISCOVER = 1; ESPNOW_SLAVE_INFO = 2; ESPNOW_HEARTBEAT = 3; + ESPNOW_SET_ACCEL_DEADZONE = 4; } message EspNowDiscover { @@ -22,11 +23,17 @@ message EspNowSlavePresence { bool used = 6; } +message EspNowAccelDeadzone { + uint32 deadzone = 1; + uint32 client_id = 2; // 0 = all slaves; otherwise only matching slave_id applies +} + message EspNowMessage { EspNowMessageType type = 1; oneof payload { EspNowDiscover discover = 2; EspNowSlavePresence slave_info = 3; EspNowSlavePresence heartbeat = 4; + EspNowAccelDeadzone accel_deadzone = 5; } } diff --git a/main/proto/uart_messages.pb.c b/main/proto/uart_messages.pb.c index 66b0399..aea702a 100644 --- a/main/proto/uart_messages.pb.c +++ b/main/proto/uart_messages.pb.c @@ -30,6 +30,12 @@ PB_BIND(alox_ClientInput, alox_ClientInput, AUTO) PB_BIND(alox_ClientInputResponse, alox_ClientInputResponse, AUTO) +PB_BIND(alox_AccelDeadzoneRequest, alox_AccelDeadzoneRequest, AUTO) + + +PB_BIND(alox_AccelDeadzoneResponse, alox_AccelDeadzoneResponse, AUTO) + + PB_BIND(alox_OtaStartPayload, alox_OtaStartPayload, AUTO) diff --git a/main/proto/uart_messages.pb.h b/main/proto/uart_messages.pb.h index 6ff236f..62d1523 100644 --- a/main/proto/uart_messages.pb.h +++ b/main/proto/uart_messages.pb.h @@ -17,6 +17,7 @@ typedef enum _alox_MessageType { alox_MessageType_VERSION = 3, alox_MessageType_CLIENT_INFO = 4, alox_MessageType_CLIENT_INPUT = 5, + alox_MessageType_ACCEL_DEADZONE = 6, alox_MessageType_OTA_START = 16, alox_MessageType_OTA_PAYLOAD = 17, alox_MessageType_OTA_END = 18, @@ -63,6 +64,23 @@ typedef struct _alox_ClientInputResponse { pb_callback_t clients; } alox_ClientInputResponse; +/* write=false: read deadzone; write=true: apply deadzone (LSB per axis, raw accel units). + client_id 0 = local BMA456 on this node; >0 = slave id on master; ignored on slave. + all_clients = true (master only): push deadzone to every registered slave via ESP-NOW. */ +typedef struct _alox_AccelDeadzoneRequest { + bool write; + uint32_t deadzone; + uint32_t client_id; + bool all_clients; +} alox_AccelDeadzoneRequest; + +typedef struct _alox_AccelDeadzoneResponse { + uint32_t deadzone; + uint32_t client_id; + bool success; + uint32_t slaves_updated; +} alox_AccelDeadzoneResponse; + typedef struct _alox_OtaStartPayload { uint32_t total_size; uint32_t block_size; @@ -95,6 +113,8 @@ typedef struct _alox_UartMessage { alox_OtaPayload ota_payload; alox_OtaEndPayload ota_end; alox_OtaStatusPayload ota_status; + alox_AccelDeadzoneRequest accel_deadzone_request; + alox_AccelDeadzoneResponse accel_deadzone_response; } payload; } alox_UartMessage; @@ -122,6 +142,8 @@ extern "C" { + + /* Initializer values for message structs */ #define alox_UartMessage_init_default {_alox_MessageType_MIN, 0, {alox_Ack_init_default}} #define alox_Ack_init_default {0} @@ -131,6 +153,8 @@ extern "C" { #define alox_ClientInfoResponse_init_default {{{NULL}, NULL}} #define alox_ClientInput_init_default {0, 0, 0, 0} #define alox_ClientInputResponse_init_default {{{NULL}, NULL}} +#define alox_AccelDeadzoneRequest_init_default {0, 0, 0, 0} +#define alox_AccelDeadzoneResponse_init_default {0, 0, 0, 0} #define alox_OtaStartPayload_init_default {0, 0} #define alox_OtaPayload_init_default {0, 0, {{NULL}, NULL}} #define alox_OtaEndPayload_init_default {0} @@ -143,6 +167,8 @@ extern "C" { #define alox_ClientInfoResponse_init_zero {{{NULL}, NULL}} #define alox_ClientInput_init_zero {0, 0, 0, 0} #define alox_ClientInputResponse_init_zero {{{NULL}, NULL}} +#define alox_AccelDeadzoneRequest_init_zero {0, 0, 0, 0} +#define alox_AccelDeadzoneResponse_init_zero {0, 0, 0, 0} #define alox_OtaStartPayload_init_zero {0, 0} #define alox_OtaPayload_init_zero {0, 0, {{NULL}, NULL}} #define alox_OtaEndPayload_init_zero {0} @@ -165,6 +191,14 @@ extern "C" { #define alox_ClientInput_lage_y_tag 3 #define alox_ClientInput_bitmask_tag 4 #define alox_ClientInputResponse_clients_tag 1 +#define alox_AccelDeadzoneRequest_write_tag 1 +#define alox_AccelDeadzoneRequest_deadzone_tag 2 +#define alox_AccelDeadzoneRequest_client_id_tag 3 +#define alox_AccelDeadzoneRequest_all_clients_tag 4 +#define alox_AccelDeadzoneResponse_deadzone_tag 1 +#define alox_AccelDeadzoneResponse_client_id_tag 2 +#define alox_AccelDeadzoneResponse_success_tag 3 +#define alox_AccelDeadzoneResponse_slaves_updated_tag 4 #define alox_OtaStartPayload_total_size_tag 1 #define alox_OtaStartPayload_block_size_tag 2 #define alox_OtaPayload_block_id_tag 1 @@ -182,6 +216,8 @@ extern "C" { #define alox_UartMessage_ota_payload_tag 8 #define alox_UartMessage_ota_end_tag 9 #define alox_UartMessage_ota_status_tag 10 +#define alox_UartMessage_accel_deadzone_request_tag 11 +#define alox_UartMessage_accel_deadzone_response_tag 12 /* Struct field encoding specification for nanopb */ #define alox_UartMessage_FIELDLIST(X, a) \ @@ -194,7 +230,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,client_input_response,payload.client X(a, STATIC, ONEOF, MESSAGE, (payload,ota_start,payload.ota_start), 7) \ X(a, STATIC, ONEOF, MESSAGE, (payload,ota_payload,payload.ota_payload), 8) \ X(a, STATIC, ONEOF, MESSAGE, (payload,ota_end,payload.ota_end), 9) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,ota_status,payload.ota_status), 10) +X(a, STATIC, ONEOF, MESSAGE, (payload,ota_status,payload.ota_status), 10) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,accel_deadzone_request,payload.accel_deadzone_request), 11) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,accel_deadzone_response,payload.accel_deadzone_response), 12) #define alox_UartMessage_CALLBACK NULL #define alox_UartMessage_DEFAULT NULL #define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack @@ -206,6 +244,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,ota_status,payload.ota_status), 10) #define alox_UartMessage_payload_ota_payload_MSGTYPE alox_OtaPayload #define alox_UartMessage_payload_ota_end_MSGTYPE alox_OtaEndPayload #define alox_UartMessage_payload_ota_status_MSGTYPE alox_OtaStatusPayload +#define alox_UartMessage_payload_accel_deadzone_request_MSGTYPE alox_AccelDeadzoneRequest +#define alox_UartMessage_payload_accel_deadzone_response_MSGTYPE alox_AccelDeadzoneResponse #define alox_Ack_FIELDLIST(X, a) \ @@ -254,6 +294,22 @@ X(a, CALLBACK, REPEATED, MESSAGE, clients, 1) #define alox_ClientInputResponse_DEFAULT NULL #define alox_ClientInputResponse_clients_MSGTYPE alox_ClientInput +#define alox_AccelDeadzoneRequest_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, write, 1) \ +X(a, STATIC, SINGULAR, UINT32, deadzone, 2) \ +X(a, STATIC, SINGULAR, UINT32, client_id, 3) \ +X(a, STATIC, SINGULAR, BOOL, all_clients, 4) +#define alox_AccelDeadzoneRequest_CALLBACK NULL +#define alox_AccelDeadzoneRequest_DEFAULT NULL + +#define alox_AccelDeadzoneResponse_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, deadzone, 1) \ +X(a, STATIC, SINGULAR, UINT32, client_id, 2) \ +X(a, STATIC, SINGULAR, BOOL, success, 3) \ +X(a, STATIC, SINGULAR, UINT32, slaves_updated, 4) +#define alox_AccelDeadzoneResponse_CALLBACK NULL +#define alox_AccelDeadzoneResponse_DEFAULT NULL + #define alox_OtaStartPayload_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, total_size, 1) \ X(a, STATIC, SINGULAR, UINT32, block_size, 2) @@ -285,6 +341,8 @@ extern const pb_msgdesc_t alox_ClientInfo_msg; extern const pb_msgdesc_t alox_ClientInfoResponse_msg; extern const pb_msgdesc_t alox_ClientInput_msg; extern const pb_msgdesc_t alox_ClientInputResponse_msg; +extern const pb_msgdesc_t alox_AccelDeadzoneRequest_msg; +extern const pb_msgdesc_t alox_AccelDeadzoneResponse_msg; extern const pb_msgdesc_t alox_OtaStartPayload_msg; extern const pb_msgdesc_t alox_OtaPayload_msg; extern const pb_msgdesc_t alox_OtaEndPayload_msg; @@ -299,6 +357,8 @@ extern const pb_msgdesc_t alox_OtaStatusPayload_msg; #define alox_ClientInfoResponse_fields &alox_ClientInfoResponse_msg #define alox_ClientInput_fields &alox_ClientInput_msg #define alox_ClientInputResponse_fields &alox_ClientInputResponse_msg +#define alox_AccelDeadzoneRequest_fields &alox_AccelDeadzoneRequest_msg +#define alox_AccelDeadzoneResponse_fields &alox_AccelDeadzoneResponse_msg #define alox_OtaStartPayload_fields &alox_OtaStartPayload_msg #define alox_OtaPayload_fields &alox_OtaPayload_msg #define alox_OtaEndPayload_fields &alox_OtaEndPayload_msg @@ -313,6 +373,8 @@ extern const pb_msgdesc_t alox_OtaStatusPayload_msg; /* alox_ClientInputResponse_size depends on runtime parameters */ /* alox_OtaPayload_size depends on runtime parameters */ #define ALOX_MAIN_PROTO_UART_MESSAGES_PB_H_MAX_SIZE alox_ClientInput_size +#define alox_AccelDeadzoneRequest_size 16 +#define alox_AccelDeadzoneResponse_size 20 #define alox_Ack_size 0 #define alox_ClientInput_size 22 #define alox_OtaEndPayload_size 6 diff --git a/main/proto/uart_messages.proto b/main/proto/uart_messages.proto index ed3abfc..d1b03c9 100644 --- a/main/proto/uart_messages.proto +++ b/main/proto/uart_messages.proto @@ -9,6 +9,7 @@ enum MessageType { VERSION = 3; CLIENT_INFO = 4; CLIENT_INPUT = 5; + ACCEL_DEADZONE = 6; OTA_START = 16; OTA_PAYLOAD = 17; OTA_END = 18; @@ -28,6 +29,8 @@ message UartMessage { OtaPayload ota_payload = 8; OtaEndPayload ota_end = 9; OtaStatusPayload ota_status = 10; + AccelDeadzoneRequest accel_deadzone_request = 11; + AccelDeadzoneResponse accel_deadzone_response = 12; } } @@ -67,6 +70,23 @@ message ClientInputResponse { repeated ClientInput clients = 1; } +// write=false: read deadzone; write=true: apply deadzone (LSB per axis, raw accel units). +// client_id 0 = local BMA456 on this node; >0 = slave id on master; ignored on slave. +// all_clients = true (master only): push deadzone to every registered slave via ESP-NOW. +message AccelDeadzoneRequest { + bool write = 1; + uint32 deadzone = 2; + uint32 client_id = 3; + bool all_clients = 4; +} + +message AccelDeadzoneResponse { + uint32 deadzone = 1; + uint32 client_id = 2; + bool success = 3; + uint32 slaves_updated = 4; +} + message OtaStartPayload { uint32 total_size = 1; uint32 block_size = 2;