diff --git a/goTool/api_serve.go b/goTool/api_serve.go
index 419a384..1483ef9 100644
--- a/goTool/api_serve.go
+++ b/goTool/api_serve.go
@@ -64,6 +64,11 @@ type findMeAPIResponse struct {
Error string `json:"error,omitempty"`
}
+type resetIdMapAPIResponse struct {
+ Success bool `json:"success"`
+ Error string `json:"error,omitempty"`
+}
+
type restartAPIRequest struct {
ClientID uint32 `json:"client_id"`
}
@@ -139,6 +144,9 @@ func mountServeAPI(mux *http.ServeMux, link *managedSerial, hub *wsHub, streamCt
}
serveRestart(w, r, link)
})
+ mux.HandleFunc("POST /api/clients/reset-id-map", func(w http.ResponseWriter, r *http.Request) {
+ serveResetClientIdMap(w, r, link)
+ })
mux.HandleFunc("/api/log-level", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
@@ -361,6 +369,23 @@ func serveRestart(w http.ResponseWriter, r *http.Request, link *managedSerial) {
writeJSON(w, http.StatusOK, restartAPIResponse{Success: true, ClientID: body.ClientID})
}
+func serveResetClientIdMap(w http.ResponseWriter, r *http.Request, link *managedSerial) {
+ if !link.IsConnected() {
+ writeJSON(w, http.StatusServiceUnavailable, resetIdMapAPIResponse{Error: "host not connected"})
+ return
+ }
+ resp, err := link.resetClientIdMap()
+ if err != nil {
+ writeJSON(w, http.StatusServiceUnavailable, resetIdMapAPIResponse{Error: err.Error()})
+ return
+ }
+ if !resp.GetSuccess() {
+ writeJSON(w, http.StatusOK, resetIdMapAPIResponse{Success: false, Error: "rejected by device"})
+ return
+ }
+ writeJSON(w, http.StatusOK, resetIdMapAPIResponse{Success: true})
+}
+
func serveFindMe(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body findMeAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
diff --git a/goTool/autotest/config.go b/goTool/autotest/config.go
index 99fda9e..60c0e91 100644
--- a/goTool/autotest/config.go
+++ b/goTool/autotest/config.go
@@ -84,13 +84,7 @@ func NewBench(cfg Config) (*Bench, error) {
return nil, fmt.Errorf("config %q slave %q: %w", cfg.ID, s.ID, err)
}
s.MAC = mac
- if s.ClientID == nil {
- parts := strings.Split(mac, ":")
- var last byte
- fmt.Sscanf(parts[5], "%02x", &last)
- id := uint(last)
- s.ClientID = &id
- }
+ /* client_id is master-assigned (sequential); omit until known from `gotool clients`. */
b.slaveByID[s.ID] = s
}
return b, nil
@@ -112,6 +106,9 @@ func (b *Bench) ResolveClientID(slaveID string) (uint32, error) {
if err != nil {
return 0, err
}
+ if s.ClientID == nil {
+ return 0, fmt.Errorf("slave %q: client_id required (master-assigned; set in config after join)", slaveID)
+ }
return uint32(*s.ClientID), nil
}
diff --git a/goTool/autotest/config_test.go b/goTool/autotest/config_test.go
index 2c41f6e..96d6537 100644
--- a/goTool/autotest/config_test.go
+++ b/goTool/autotest/config_test.go
@@ -2,7 +2,7 @@ package autotest
import "testing"
-func TestNewBenchClientIDFromMAC(t *testing.T) {
+func TestNewBenchNoDefaultClientIDFromMAC(t *testing.T) {
cfg := Config{
ID: "t",
Network: 1,
@@ -20,8 +20,39 @@ func TestNewBenchClientIDFromMAC(t *testing.T) {
t.Fatal(err)
}
s, _ := b.Slave("pod")
- if *s.ClientID != 16 {
- t.Fatalf("client_id=%d want 16", *s.ClientID)
+ if s.ClientID != nil {
+ t.Fatalf("client_id should be unset, got %d", *s.ClientID)
+ }
+ if _, err := b.ResolveClientID("pod"); err == nil {
+ t.Fatal("ResolveClientID should fail without client_id")
+ }
+}
+
+func TestNewBenchExplicitClientID(t *testing.T) {
+ id := uint(3)
+ cfg := Config{
+ ID: "t",
+ Network: 1,
+ MasterMAC: "aa:bb:cc:dd:ee:ff",
+ UART: UARTConfig{
+ Master: "/dev/ttyUSB0",
+ },
+ Slaves: []SlaveNode{{
+ ID: "pod",
+ MAC: "50:78:7d:18:01:10",
+ ClientID: &id,
+ }},
+ }
+ b, err := NewBench(cfg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got, err := b.ResolveClientID("pod")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got != 3 {
+ t.Fatalf("client_id=%d want 3", got)
}
}
diff --git a/goTool/autotest/runner.go b/goTool/autotest/runner.go
index 0590c16..65f49b0 100644
--- a/goTool/autotest/runner.go
+++ b/goTool/autotest/runner.go
@@ -195,7 +195,7 @@ func checkClients(bench *Bench, step Step, client MasterClient) error {
if found == nil {
return fmt.Errorf("slave %q mac %s not in client list", name, want.MAC)
}
- if uint32(*want.ClientID) != found.GetId() {
+ if want.ClientID != nil && uint32(*want.ClientID) != found.GetId() {
return fmt.Errorf("slave %q id=%d want client_id=%d", name, found.GetId(), *want.ClientID)
}
if e.Available != nil && found.GetAvailable() != *e.Available {
diff --git a/goTool/cmd_clients_reset_ids.go b/goTool/cmd_clients_reset_ids.go
new file mode 100644
index 0000000..8bf52bb
--- /dev/null
+++ b/goTool/cmd_clients_reset_ids.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "fmt"
+
+ "google.golang.org/protobuf/proto"
+ "powerpod/gotool/pb"
+)
+
+func runClientsResetIds(sp *serialPort) error {
+ resp, err := sp.resetClientIdMap()
+ if err != nil {
+ return err
+ }
+ if !resp.GetSuccess() {
+ return fmt.Errorf("client id map reset rejected")
+ }
+ fmt.Println("client id map cleared (master map + live registry; online slaves notified)")
+ return nil
+}
+
+func decodeClientIdMapResetPayload(payload []byte) (*pb.ClientIdMapResetResponse, error) {
+ if len(payload) < 2 {
+ return nil, fmt.Errorf("short client id map reset response")
+ }
+ if payload[0] != byte(pb.MessageType_CLIENT_ID_MAP_RESET) {
+ return nil, fmt.Errorf("unexpected command id 0x%02x (want 0x%02x)",
+ payload[0], byte(pb.MessageType_CLIENT_ID_MAP_RESET))
+ }
+ var respMsg pb.UartMessage
+ if err := proto.Unmarshal(payload[1:], &respMsg); err != nil {
+ return nil, fmt.Errorf("decode: %w", err)
+ }
+ r := respMsg.GetClientIdMapResetResponse()
+ if r == nil {
+ return nil, fmt.Errorf("missing client_id_map_reset_response")
+ }
+ return r, nil
+}
+
+func (s *serialPort) resetClientIdMap() (*pb.ClientIdMapResetResponse, error) {
+ payload, err := s.exchange(byte(pb.MessageType_CLIENT_ID_MAP_RESET), "CLIENT_ID_MAP_RESET")
+ if err != nil {
+ return nil, err
+ }
+ return decodeClientIdMapResetPayload(payload)
+}
+
+func (m *managedSerial) resetClientIdMap() (*pb.ClientIdMapResetResponse, error) {
+ payload, err := m.exchange(byte(pb.MessageType_CLIENT_ID_MAP_RESET), "CLIENT_ID_MAP_RESET")
+ if err != nil {
+ return nil, err
+ }
+ return decodeClientIdMapResetPayload(payload)
+}
diff --git a/goTool/main.go b/goTool/main.go
index a611c19..59a07bc 100644
--- a/goTool/main.go
+++ b/goTool/main.go
@@ -16,7 +16,8 @@ func usage() {
fmt.Fprintf(os.Stderr, " test uses uart.master from bench config when -port is omitted\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")
+ fmt.Fprintf(os.Stderr, " clients registered ESP-NOW slaves on the master\n")
+ fmt.Fprintf(os.Stderr, " clients-reset-ids clear master id map + live registry (notifies online slaves)\n")
fmt.Fprintf(os.Stderr, " deadzone get/set accelerometer deadzone (LSB)\n")
fmt.Fprintf(os.Stderr, " tap-config get/set BMA456 tap detection parameters\n")
fmt.Fprintf(os.Stderr, " tap-notify get/set which tap kinds notify via ESP-NOW\n")
@@ -72,7 +73,7 @@ func main() {
bleName: *bleName,
settle: time.Duration(*bleSettle * float64(time.Second)),
}, flag.Args()[1:])
- case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-config", "tap_config", "tap-notify", "tap_notify", "cache-status", "cache_status", "accel-stream", "accel_stream", "unicast-test", "unicast_test", "echo-ping", "echo_ping", "ble-rtt", "ble_rtt", "ble-stream", "ble_stream", "led-ring", "led_ring", "find-me", "find_me", "restart", "log-level", "log_level", "ota", "ota-progress", "ota_progress":
+ case "version", "clients", "client-info", "clients-reset-ids", "clients_reset_ids", "deadzone", "accel-deadzone", "tap-config", "tap_config", "tap-notify", "tap_notify", "cache-status", "cache_status", "accel-stream", "accel_stream", "unicast-test", "unicast_test", "echo-ping", "echo_ping", "ble-rtt", "ble_rtt", "ble-stream", "ble_stream", "led-ring", "led_ring", "find-me", "find_me", "restart", "log-level", "log_level", "ota", "ota-progress", "ota_progress":
if *useBLE && *portName != "" {
fmt.Fprintf(os.Stderr, "use either -port or -ble, not both\n")
os.Exit(2)
@@ -112,6 +113,13 @@ func main() {
runErr = runVersionHost(h)
case "clients", "client-info":
runErr = runClientsHost(h)
+ case "clients-reset-ids", "clients_reset_ids":
+ sp, ok := h.(*serialPort)
+ if !ok {
+ runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
+ break
+ }
+ runErr = runClientsResetIds(sp)
case "cache-status", "cache_status":
runErr = runCacheStatusHost(h)
case "accel-stream", "accel_stream":
diff --git a/goTool/pb/uart_messages.pb.go b/goTool/pb/uart_messages.pb.go
index 742504e..73092cc 100644
--- a/goTool/pb/uart_messages.pb.go
+++ b/goTool/pb/uart_messages.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
-// protoc v7.36.0
+// protoc v7.36.1
// source: uart_messages.proto
package pb
@@ -54,6 +54,10 @@ const (
MessageType_TAP_CONFIG MessageType = 32
// * Master → host (BLE only): periodic pushed cache snapshot with ESP timestamp.
MessageType_BLE_STREAM_TICK MessageType = 33
+ // *
+ // Host → device: clear master client-id NVS map + live registry (and notify
+ // online slaves), or on a slave clear local master/id binding only.
+ MessageType_CLIENT_ID_MAP_RESET MessageType = 34
)
// Enum value maps for MessageType.
@@ -84,6 +88,7 @@ var (
31: "SET_LOG_LEVEL",
32: "TAP_CONFIG",
33: "BLE_STREAM_TICK",
+ 34: "CLIENT_ID_MAP_RESET",
}
MessageType_value = map[string]int32{
"UNKNOWN": 0,
@@ -111,6 +116,7 @@ var (
"SET_LOG_LEVEL": 31,
"TAP_CONFIG": 32,
"BLE_STREAM_TICK": 33,
+ "CLIENT_ID_MAP_RESET": 34,
}
)
@@ -234,6 +240,8 @@ type UartMessage struct {
// *UartMessage_TapConfigRequest
// *UartMessage_TapConfigResponse
// *UartMessage_BleStreamTick
+ // *UartMessage_ClientIdMapResetRequest
+ // *UartMessage_ClientIdMapResetResponse
Payload isUartMessage_Payload `protobuf_oneof:"payload"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
@@ -607,6 +615,24 @@ func (x *UartMessage) GetBleStreamTick() *BleStreamTick {
return nil
}
+func (x *UartMessage) GetClientIdMapResetRequest() *ClientIdMapResetRequest {
+ if x != nil {
+ if x, ok := x.Payload.(*UartMessage_ClientIdMapResetRequest); ok {
+ return x.ClientIdMapResetRequest
+ }
+ }
+ return nil
+}
+
+func (x *UartMessage) GetClientIdMapResetResponse() *ClientIdMapResetResponse {
+ if x != nil {
+ if x, ok := x.Payload.(*UartMessage_ClientIdMapResetResponse); ok {
+ return x.ClientIdMapResetResponse
+ }
+ }
+ return nil
+}
+
type isUartMessage_Payload interface {
isUartMessage_Payload()
}
@@ -755,6 +781,14 @@ type UartMessage_BleStreamTick struct {
BleStreamTick *BleStreamTick `protobuf:"bytes,41,opt,name=ble_stream_tick,json=bleStreamTick,proto3,oneof"`
}
+type UartMessage_ClientIdMapResetRequest struct {
+ ClientIdMapResetRequest *ClientIdMapResetRequest `protobuf:"bytes,42,opt,name=client_id_map_reset_request,json=clientIdMapResetRequest,proto3,oneof"`
+}
+
+type UartMessage_ClientIdMapResetResponse struct {
+ ClientIdMapResetResponse *ClientIdMapResetResponse `protobuf:"bytes,43,opt,name=client_id_map_reset_response,json=clientIdMapResetResponse,proto3,oneof"`
+}
+
func (*UartMessage_AckPayload) isUartMessage_Payload() {}
func (*UartMessage_EchoPayload) isUartMessage_Payload() {}
@@ -827,6 +861,10 @@ func (*UartMessage_TapConfigResponse) isUartMessage_Payload() {}
func (*UartMessage_BleStreamTick) isUartMessage_Payload() {}
+func (*UartMessage_ClientIdMapResetRequest) isUartMessage_Payload() {}
+
+func (*UartMessage_ClientIdMapResetResponse) isUartMessage_Payload() {}
+
type Ack struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
@@ -3071,6 +3109,87 @@ func (x *RestartResponse) GetClientId() uint32 {
return 0
}
+// * Host → master or slave: clear persisted client-id assignment state.
+type ClientIdMapResetRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ClientIdMapResetRequest) Reset() {
+ *x = ClientIdMapResetRequest{}
+ mi := &file_uart_messages_proto_msgTypes[36]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ClientIdMapResetRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClientIdMapResetRequest) ProtoMessage() {}
+
+func (x *ClientIdMapResetRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_uart_messages_proto_msgTypes[36]
+ 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 ClientIdMapResetRequest.ProtoReflect.Descriptor instead.
+func (*ClientIdMapResetRequest) Descriptor() ([]byte, []int) {
+ return file_uart_messages_proto_rawDescGZIP(), []int{36}
+}
+
+type ClientIdMapResetResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ClientIdMapResetResponse) Reset() {
+ *x = ClientIdMapResetResponse{}
+ mi := &file_uart_messages_proto_msgTypes[37]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ClientIdMapResetResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClientIdMapResetResponse) ProtoMessage() {}
+
+func (x *ClientIdMapResetResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_uart_messages_proto_msgTypes[37]
+ 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 ClientIdMapResetResponse.ProtoReflect.Descriptor instead.
+func (*ClientIdMapResetResponse) Descriptor() ([]byte, []int) {
+ return file_uart_messages_proto_rawDescGZIP(), []int{37}
+}
+
+func (x *ClientIdMapResetResponse) GetSuccess() bool {
+ if x != nil {
+ return x.Success
+ }
+ return false
+}
+
// * Host → master: read/write global log level (esp_log_level_set("*", …)).
type SetLogLevelRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -3083,7 +3202,7 @@ type SetLogLevelRequest struct {
func (x *SetLogLevelRequest) Reset() {
*x = SetLogLevelRequest{}
- mi := &file_uart_messages_proto_msgTypes[36]
+ mi := &file_uart_messages_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3095,7 +3214,7 @@ func (x *SetLogLevelRequest) String() string {
func (*SetLogLevelRequest) ProtoMessage() {}
func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[36]
+ mi := &file_uart_messages_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3108,7 +3227,7 @@ func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SetLogLevelRequest.ProtoReflect.Descriptor instead.
func (*SetLogLevelRequest) Descriptor() ([]byte, []int) {
- return file_uart_messages_proto_rawDescGZIP(), []int{36}
+ return file_uart_messages_proto_rawDescGZIP(), []int{38}
}
func (x *SetLogLevelRequest) GetWrite() bool {
@@ -3135,7 +3254,7 @@ type SetLogLevelResponse struct {
func (x *SetLogLevelResponse) Reset() {
*x = SetLogLevelResponse{}
- mi := &file_uart_messages_proto_msgTypes[37]
+ mi := &file_uart_messages_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3147,7 +3266,7 @@ func (x *SetLogLevelResponse) String() string {
func (*SetLogLevelResponse) ProtoMessage() {}
func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[37]
+ mi := &file_uart_messages_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3160,7 +3279,7 @@ func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SetLogLevelResponse.ProtoReflect.Descriptor instead.
func (*SetLogLevelResponse) Descriptor() ([]byte, []int) {
- return file_uart_messages_proto_rawDescGZIP(), []int{37}
+ return file_uart_messages_proto_rawDescGZIP(), []int{39}
}
func (x *SetLogLevelResponse) GetSuccess() bool {
@@ -3199,7 +3318,7 @@ type TapConfigRequest struct {
func (x *TapConfigRequest) Reset() {
*x = TapConfigRequest{}
- mi := &file_uart_messages_proto_msgTypes[38]
+ mi := &file_uart_messages_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3211,7 +3330,7 @@ func (x *TapConfigRequest) String() string {
func (*TapConfigRequest) ProtoMessage() {}
func (x *TapConfigRequest) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[38]
+ mi := &file_uart_messages_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3224,7 +3343,7 @@ func (x *TapConfigRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use TapConfigRequest.ProtoReflect.Descriptor instead.
func (*TapConfigRequest) Descriptor() ([]byte, []int) {
- return file_uart_messages_proto_rawDescGZIP(), []int{38}
+ return file_uart_messages_proto_rawDescGZIP(), []int{40}
}
func (x *TapConfigRequest) GetWrite() bool {
@@ -3331,7 +3450,7 @@ type TapConfigResponse struct {
func (x *TapConfigResponse) Reset() {
*x = TapConfigResponse{}
- mi := &file_uart_messages_proto_msgTypes[39]
+ mi := &file_uart_messages_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3343,7 +3462,7 @@ func (x *TapConfigResponse) String() string {
func (*TapConfigResponse) ProtoMessage() {}
func (x *TapConfigResponse) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[39]
+ mi := &file_uart_messages_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3356,7 +3475,7 @@ func (x *TapConfigResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use TapConfigResponse.ProtoReflect.Descriptor instead.
func (*TapConfigResponse) Descriptor() ([]byte, []int) {
- return file_uart_messages_proto_rawDescGZIP(), []int{39}
+ return file_uart_messages_proto_rawDescGZIP(), []int{41}
}
func (x *TapConfigResponse) GetClientId() uint32 {
@@ -3453,7 +3572,7 @@ type OtaStartPayload struct {
func (x *OtaStartPayload) Reset() {
*x = OtaStartPayload{}
- mi := &file_uart_messages_proto_msgTypes[40]
+ mi := &file_uart_messages_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3465,7 +3584,7 @@ func (x *OtaStartPayload) String() string {
func (*OtaStartPayload) ProtoMessage() {}
func (x *OtaStartPayload) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[40]
+ mi := &file_uart_messages_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3478,7 +3597,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{40}
+ return file_uart_messages_proto_rawDescGZIP(), []int{42}
}
func (x *OtaStartPayload) GetTotalSize() uint32 {
@@ -3499,7 +3618,7 @@ type OtaPayload struct {
func (x *OtaPayload) Reset() {
*x = OtaPayload{}
- mi := &file_uart_messages_proto_msgTypes[41]
+ mi := &file_uart_messages_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3511,7 +3630,7 @@ func (x *OtaPayload) String() string {
func (*OtaPayload) ProtoMessage() {}
func (x *OtaPayload) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[41]
+ mi := &file_uart_messages_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3524,7 +3643,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{41}
+ return file_uart_messages_proto_rawDescGZIP(), []int{43}
}
func (x *OtaPayload) GetSeq() uint32 {
@@ -3550,7 +3669,7 @@ type OtaEndPayload struct {
func (x *OtaEndPayload) Reset() {
*x = OtaEndPayload{}
- mi := &file_uart_messages_proto_msgTypes[42]
+ mi := &file_uart_messages_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3562,7 +3681,7 @@ func (x *OtaEndPayload) String() string {
func (*OtaEndPayload) ProtoMessage() {}
func (x *OtaEndPayload) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[42]
+ mi := &file_uart_messages_proto_msgTypes[44]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3575,7 +3694,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{42}
+ return file_uart_messages_proto_rawDescGZIP(), []int{44}
}
// Device → host status (also used as ACK after each 4 KiB written).
@@ -3592,7 +3711,7 @@ type OtaStatusPayload struct {
func (x *OtaStatusPayload) Reset() {
*x = OtaStatusPayload{}
- mi := &file_uart_messages_proto_msgTypes[43]
+ mi := &file_uart_messages_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3604,7 +3723,7 @@ func (x *OtaStatusPayload) String() string {
func (*OtaStatusPayload) ProtoMessage() {}
func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[43]
+ mi := &file_uart_messages_proto_msgTypes[45]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3617,7 +3736,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{43}
+ return file_uart_messages_proto_rawDescGZIP(), []int{45}
}
func (x *OtaStatusPayload) GetStatus() uint32 {
@@ -3658,7 +3777,7 @@ type OtaSlaveProgressRequest struct {
func (x *OtaSlaveProgressRequest) Reset() {
*x = OtaSlaveProgressRequest{}
- mi := &file_uart_messages_proto_msgTypes[44]
+ mi := &file_uart_messages_proto_msgTypes[46]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3670,7 +3789,7 @@ func (x *OtaSlaveProgressRequest) String() string {
func (*OtaSlaveProgressRequest) ProtoMessage() {}
func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[44]
+ mi := &file_uart_messages_proto_msgTypes[46]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3683,7 +3802,7 @@ func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaSlaveProgressRequest.ProtoReflect.Descriptor instead.
func (*OtaSlaveProgressRequest) Descriptor() ([]byte, []int) {
- return file_uart_messages_proto_rawDescGZIP(), []int{44}
+ return file_uart_messages_proto_rawDescGZIP(), []int{46}
}
func (x *OtaSlaveProgressRequest) GetClientId() uint32 {
@@ -3707,7 +3826,7 @@ type OtaSlaveProgressEntry struct {
func (x *OtaSlaveProgressEntry) Reset() {
*x = OtaSlaveProgressEntry{}
- mi := &file_uart_messages_proto_msgTypes[45]
+ mi := &file_uart_messages_proto_msgTypes[47]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3719,7 +3838,7 @@ func (x *OtaSlaveProgressEntry) String() string {
func (*OtaSlaveProgressEntry) ProtoMessage() {}
func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[45]
+ mi := &file_uart_messages_proto_msgTypes[47]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3732,7 +3851,7 @@ func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaSlaveProgressEntry.ProtoReflect.Descriptor instead.
func (*OtaSlaveProgressEntry) Descriptor() ([]byte, []int) {
- return file_uart_messages_proto_rawDescGZIP(), []int{45}
+ return file_uart_messages_proto_rawDescGZIP(), []int{47}
}
func (x *OtaSlaveProgressEntry) GetClientId() uint32 {
@@ -3783,7 +3902,7 @@ type OtaSlaveProgressResponse struct {
func (x *OtaSlaveProgressResponse) Reset() {
*x = OtaSlaveProgressResponse{}
- mi := &file_uart_messages_proto_msgTypes[46]
+ mi := &file_uart_messages_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -3795,7 +3914,7 @@ func (x *OtaSlaveProgressResponse) String() string {
func (*OtaSlaveProgressResponse) ProtoMessage() {}
func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message {
- mi := &file_uart_messages_proto_msgTypes[46]
+ mi := &file_uart_messages_proto_msgTypes[48]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3808,7 +3927,7 @@ func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use OtaSlaveProgressResponse.ProtoReflect.Descriptor instead.
func (*OtaSlaveProgressResponse) Descriptor() ([]byte, []int) {
- return file_uart_messages_proto_rawDescGZIP(), []int{46}
+ return file_uart_messages_proto_rawDescGZIP(), []int{48}
}
func (x *OtaSlaveProgressResponse) GetActive() bool {
@@ -3850,7 +3969,7 @@ var File_uart_messages_proto protoreflect.FileDescriptor
const file_uart_messages_proto_rawDesc = "" +
"\n" +
- "\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\x92\x16\n" +
+ "\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\xd3\x17\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" +
@@ -3892,7 +4011,9 @@ const file_uart_messages_proto_rawDesc = "" +
"\x16set_log_level_response\x18& \x01(\v2\x19.alox.SetLogLevelResponseH\x00R\x13setLogLevelResponse\x12F\n" +
"\x12tap_config_request\x18' \x01(\v2\x16.alox.TapConfigRequestH\x00R\x10tapConfigRequest\x12I\n" +
"\x13tap_config_response\x18( \x01(\v2\x17.alox.TapConfigResponseH\x00R\x11tapConfigResponse\x12=\n" +
- "\x0fble_stream_tick\x18) \x01(\v2\x13.alox.BleStreamTickH\x00R\rbleStreamTickB\t\n" +
+ "\x0fble_stream_tick\x18) \x01(\v2\x13.alox.BleStreamTickH\x00R\rbleStreamTick\x12]\n" +
+ "\x1bclient_id_map_reset_request\x18* \x01(\v2\x1d.alox.ClientIdMapResetRequestH\x00R\x17clientIdMapResetRequest\x12`\n" +
+ "\x1cclient_id_map_reset_response\x18+ \x01(\v2\x1e.alox.ClientIdMapResetResponseH\x00R\x18clientIdMapResetResponseB\t\n" +
"\apayload\"\x05\n" +
"\x03Ack\"!\n" +
"\vEchoPayload\x12\x12\n" +
@@ -4060,7 +4181,10 @@ const file_uart_messages_proto_rawDesc = "" +
"\tclient_id\x18\x01 \x01(\rR\bclientId\"H\n" +
"\x0fRestartResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1b\n" +
- "\tclient_id\x18\x02 \x01(\rR\bclientId\"@\n" +
+ "\tclient_id\x18\x02 \x01(\rR\bclientId\"\x19\n" +
+ "\x17ClientIdMapResetRequest\"4\n" +
+ "\x18ClientIdMapResetResponse\x12\x18\n" +
+ "\asuccess\x18\x01 \x01(\bR\asuccess\"@\n" +
"\x12SetLogLevelRequest\x12\x14\n" +
"\x05write\x18\x01 \x01(\bR\x05write\x12\x14\n" +
"\x05level\x18\x02 \x01(\rR\x05level\"E\n" +
@@ -4128,7 +4252,7 @@ const file_uart_messages_proto_rawDesc = "" +
"\x0faggregate_bytes\x18\x03 \x01(\rR\x0eaggregateBytes\x12\x1f\n" +
"\vslave_count\x18\x04 \x01(\rR\n" +
"slaveCount\x12:\n" +
- "\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\xbf\x03\n" +
+ "\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\xd8\x03\n" +
"\vMessageType\x12\v\n" +
"\aUNKNOWN\x10\x00\x12\a\n" +
"\x03ACK\x10\x01\x12\b\n" +
@@ -4157,7 +4281,8 @@ const file_uart_messages_proto_rawDesc = "" +
"\rSET_LOG_LEVEL\x10\x1f\x12\x0e\n" +
"\n" +
"TAP_CONFIG\x10 \x12\x13\n" +
- "\x0fBLE_STREAM_TICK\x10!\"\x04\b\x18\x10\x18\"\x04\b\x1c\x10\x1c*G\n" +
+ "\x0fBLE_STREAM_TICK\x10!\x12\x17\n" +
+ "\x13CLIENT_ID_MAP_RESET\x10\"\"\x04\b\x18\x10\x18\"\x04\b\x1c\x10\x1c*G\n" +
"\aTapKind\x12\f\n" +
"\bTAP_NONE\x10\x00\x12\x0e\n" +
"\n" +
@@ -4180,7 +4305,7 @@ func file_uart_messages_proto_rawDescGZIP() []byte {
}
var file_uart_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
-var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 47)
+var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 49)
var file_uart_messages_proto_goTypes = []any{
(MessageType)(0), // 0: alox.MessageType
(TapKind)(0), // 1: alox.TapKind
@@ -4220,17 +4345,19 @@ var file_uart_messages_proto_goTypes = []any{
(*EspNowFindMeResponse)(nil), // 35: alox.EspNowFindMeResponse
(*RestartRequest)(nil), // 36: alox.RestartRequest
(*RestartResponse)(nil), // 37: alox.RestartResponse
- (*SetLogLevelRequest)(nil), // 38: alox.SetLogLevelRequest
- (*SetLogLevelResponse)(nil), // 39: alox.SetLogLevelResponse
- (*TapConfigRequest)(nil), // 40: alox.TapConfigRequest
- (*TapConfigResponse)(nil), // 41: alox.TapConfigResponse
- (*OtaStartPayload)(nil), // 42: alox.OtaStartPayload
- (*OtaPayload)(nil), // 43: alox.OtaPayload
- (*OtaEndPayload)(nil), // 44: alox.OtaEndPayload
- (*OtaStatusPayload)(nil), // 45: alox.OtaStatusPayload
- (*OtaSlaveProgressRequest)(nil), // 46: alox.OtaSlaveProgressRequest
- (*OtaSlaveProgressEntry)(nil), // 47: alox.OtaSlaveProgressEntry
- (*OtaSlaveProgressResponse)(nil), // 48: alox.OtaSlaveProgressResponse
+ (*ClientIdMapResetRequest)(nil), // 38: alox.ClientIdMapResetRequest
+ (*ClientIdMapResetResponse)(nil), // 39: alox.ClientIdMapResetResponse
+ (*SetLogLevelRequest)(nil), // 40: alox.SetLogLevelRequest
+ (*SetLogLevelResponse)(nil), // 41: alox.SetLogLevelResponse
+ (*TapConfigRequest)(nil), // 42: alox.TapConfigRequest
+ (*TapConfigResponse)(nil), // 43: alox.TapConfigResponse
+ (*OtaStartPayload)(nil), // 44: alox.OtaStartPayload
+ (*OtaPayload)(nil), // 45: alox.OtaPayload
+ (*OtaEndPayload)(nil), // 46: alox.OtaEndPayload
+ (*OtaStatusPayload)(nil), // 47: alox.OtaStatusPayload
+ (*OtaSlaveProgressRequest)(nil), // 48: alox.OtaSlaveProgressRequest
+ (*OtaSlaveProgressEntry)(nil), // 49: alox.OtaSlaveProgressEntry
+ (*OtaSlaveProgressResponse)(nil), // 50: alox.OtaSlaveProgressResponse
}
var file_uart_messages_proto_depIdxs = []int32{
0, // 0: alox.UartMessage.type:type_name -> alox.MessageType
@@ -4239,16 +4366,16 @@ var file_uart_messages_proto_depIdxs = []int32{
5, // 3: alox.UartMessage.version_response:type_name -> alox.VersionResponse
7, // 4: alox.UartMessage.client_info_response:type_name -> alox.ClientInfoResponse
9, // 5: alox.UartMessage.client_input_response:type_name -> alox.ClientInputResponse
- 42, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload
- 43, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload
- 44, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload
- 45, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload
+ 44, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload
+ 45, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload
+ 46, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload
+ 47, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload
10, // 10: alox.UartMessage.accel_deadzone_request:type_name -> alox.AccelDeadzoneRequest
11, // 11: alox.UartMessage.accel_deadzone_response:type_name -> alox.AccelDeadzoneResponse
28, // 12: alox.UartMessage.espnow_unicast_test_request:type_name -> alox.EspNowUnicastTestRequest
29, // 13: alox.UartMessage.espnow_unicast_test_response:type_name -> alox.EspNowUnicastTestResponse
- 46, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest
- 48, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse
+ 48, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest
+ 50, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse
32, // 16: alox.UartMessage.led_ring_progress_request:type_name -> alox.LedRingProgressRequest
33, // 17: alox.UartMessage.led_ring_progress_response:type_name -> alox.LedRingProgressResponse
34, // 18: alox.UartMessage.espnow_find_me_request:type_name -> alox.EspNowFindMeRequest
@@ -4265,28 +4392,30 @@ var file_uart_messages_proto_depIdxs = []int32{
26, // 29: alox.UartMessage.cache_status_response:type_name -> alox.CacheStatusResponse
30, // 30: alox.UartMessage.espnow_echo_ping_request:type_name -> alox.EspNowEchoPingRequest
31, // 31: alox.UartMessage.espnow_echo_ping_response:type_name -> alox.EspNowEchoPingResponse
- 38, // 32: alox.UartMessage.set_log_level_request:type_name -> alox.SetLogLevelRequest
- 39, // 33: alox.UartMessage.set_log_level_response:type_name -> alox.SetLogLevelResponse
- 40, // 34: alox.UartMessage.tap_config_request:type_name -> alox.TapConfigRequest
- 41, // 35: alox.UartMessage.tap_config_response:type_name -> alox.TapConfigResponse
+ 40, // 32: alox.UartMessage.set_log_level_request:type_name -> alox.SetLogLevelRequest
+ 41, // 33: alox.UartMessage.set_log_level_response:type_name -> alox.SetLogLevelResponse
+ 42, // 34: alox.UartMessage.tap_config_request:type_name -> alox.TapConfigRequest
+ 43, // 35: alox.UartMessage.tap_config_response:type_name -> alox.TapConfigResponse
27, // 36: alox.UartMessage.ble_stream_tick:type_name -> alox.BleStreamTick
- 6, // 37: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo
- 8, // 38: alox.ClientInputResponse.clients:type_name -> alox.ClientInput
- 15, // 39: alox.BatterySample.lipo1:type_name -> alox.LipoReading
- 15, // 40: alox.BatterySample.lipo2:type_name -> alox.LipoReading
- 16, // 41: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample
- 1, // 42: alox.TapEvent.kind:type_name -> alox.TapKind
- 1, // 43: alox.CacheClientTap.kind:type_name -> alox.TapKind
- 23, // 44: alox.CacheClientStatus.accel:type_name -> alox.CacheClientAccel
- 24, // 45: alox.CacheClientStatus.tap:type_name -> alox.CacheClientTap
- 25, // 46: alox.CacheStatusResponse.clients:type_name -> alox.CacheClientStatus
- 25, // 47: alox.BleStreamTick.clients:type_name -> alox.CacheClientStatus
- 47, // 48: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry
- 49, // [49:49] is the sub-list for method output_type
- 49, // [49:49] is the sub-list for method input_type
- 49, // [49:49] is the sub-list for extension type_name
- 49, // [49:49] is the sub-list for extension extendee
- 0, // [0:49] is the sub-list for field type_name
+ 38, // 37: alox.UartMessage.client_id_map_reset_request:type_name -> alox.ClientIdMapResetRequest
+ 39, // 38: alox.UartMessage.client_id_map_reset_response:type_name -> alox.ClientIdMapResetResponse
+ 6, // 39: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo
+ 8, // 40: alox.ClientInputResponse.clients:type_name -> alox.ClientInput
+ 15, // 41: alox.BatterySample.lipo1:type_name -> alox.LipoReading
+ 15, // 42: alox.BatterySample.lipo2:type_name -> alox.LipoReading
+ 16, // 43: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample
+ 1, // 44: alox.TapEvent.kind:type_name -> alox.TapKind
+ 1, // 45: alox.CacheClientTap.kind:type_name -> alox.TapKind
+ 23, // 46: alox.CacheClientStatus.accel:type_name -> alox.CacheClientAccel
+ 24, // 47: alox.CacheClientStatus.tap:type_name -> alox.CacheClientTap
+ 25, // 48: alox.CacheStatusResponse.clients:type_name -> alox.CacheClientStatus
+ 25, // 49: alox.BleStreamTick.clients:type_name -> alox.CacheClientStatus
+ 49, // 50: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry
+ 51, // [51:51] is the sub-list for method output_type
+ 51, // [51:51] is the sub-list for method input_type
+ 51, // [51:51] is the sub-list for extension type_name
+ 51, // [51:51] is the sub-list for extension extendee
+ 0, // [0:51] is the sub-list for field type_name
}
func init() { file_uart_messages_proto_init() }
@@ -4331,6 +4460,8 @@ func file_uart_messages_proto_init() {
(*UartMessage_TapConfigRequest)(nil),
(*UartMessage_TapConfigResponse)(nil),
(*UartMessage_BleStreamTick)(nil),
+ (*UartMessage_ClientIdMapResetRequest)(nil),
+ (*UartMessage_ClientIdMapResetResponse)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -4338,7 +4469,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: 2,
- NumMessages: 47,
+ NumMessages: 49,
NumExtensions: 0,
NumServices: 0,
},
diff --git a/goTool/testdata/configs/example-lab.json b/goTool/testdata/configs/example-lab.json
index 52cd528..81044ab 100644
--- a/goTool/testdata/configs/example-lab.json
+++ b/goTool/testdata/configs/example-lab.json
@@ -12,7 +12,7 @@
{
"id": "pod-1",
"mac": "50:78:7d:18:01:10",
- "client_id": 16,
+ "client_id": 1,
"console": "/dev/ttyACM1"
}
]
diff --git a/goTool/webui/index.html b/goTool/webui/index.html
index c143dea..1215b62 100644
--- a/goTool/webui/index.html
+++ b/goTool/webui/index.html
@@ -540,6 +540,12 @@
title="Master-Tap-Config an alle Slaves (ESP-NOW)">
Tap-Cfg alle
+
@@ -1957,6 +1963,26 @@
this.busy = false;
}
},
+ async resetClientIdMap() {
+ if (!confirm('ID-Map leeren? Master vergisst alle MAC→ID-Zuordnungen; Online-Slaves verlieren ihr Binding.')) {
+ return;
+ }
+ this.busy = true;
+ try {
+ const r = await fetch('/api/clients/reset-id-map', { method: 'POST' });
+ const data = await r.json();
+ if (!r.ok || !data.success) {
+ this.flash(data.error || 'ID-Map leeren fehlgeschlagen', false);
+ return;
+ }
+ this.state = { ...this.state, clients: [] };
+ this.flash('ID-Map geleert — Slaves melden sich neu an', true);
+ } catch (e) {
+ this.flash(String(e), false);
+ } finally {
+ this.busy = false;
+ }
+ },
async restart(clientId = 0) {
this.busy = true;
try {
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 738c0db..f513ecb 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -29,6 +29,7 @@ idf_component_register(
"cmd/cmd_espnow_echo_ping.c"
"cmd/cmd_espnow_find_me.c"
"cmd/cmd_restart.c"
+ "cmd/cmd_client_id_map_reset.c"
"pod_reboot.c"
"cmd/cmd_led_ring.c"
"cmd/cmd_battery.c"
diff --git a/main/client_registry.c b/main/client_registry.c
index 9385117..a049990 100644
--- a/main/client_registry.c
+++ b/main/client_registry.c
@@ -1,4 +1,5 @@
#include "client_registry.h"
+#include "pod_settings.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/idf_additions.h"
@@ -13,12 +14,68 @@ typedef struct {
} client_slot_t;
static client_slot_t s_clients[CLIENT_REGISTRY_MAX];
+static pod_id_map_t s_id_map;
static struct {
board_lipo_reading_t reading;
uint32_t updated_at;
} s_master_battery;
+static bool id_in_use(uint32_t id, const uint8_t *except_mac) {
+ for (uint8_t i = 0; i < s_id_map.count; i++) {
+ if (s_id_map.entries[i].id != id) {
+ continue;
+ }
+ if (except_mac != NULL &&
+ memcmp(s_id_map.entries[i].mac, except_mac, CLIENT_MAC_LEN) == 0) {
+ continue;
+ }
+ return true;
+ }
+ return false;
+}
+
+static int find_map_index(const uint8_t mac[CLIENT_MAC_LEN]) {
+ for (uint8_t i = 0; i < s_id_map.count; i++) {
+ if (memcmp(s_id_map.entries[i].mac, mac, CLIENT_MAC_LEN) == 0) {
+ return (int)i;
+ }
+ }
+ return -1;
+}
+
+static esp_err_t persist_id_map(void) {
+ return pod_settings_save_id_map(&s_id_map);
+}
+
+static uint32_t alloc_next_free_id(void) {
+ if (s_id_map.count >= POD_ID_MAP_MAX) {
+ return 0;
+ }
+ uint8_t start = s_id_map.next_id;
+ if (start < POD_CLIENT_ID_MIN) {
+ start = (uint8_t)POD_CLIENT_ID_MIN;
+ }
+ for (uint32_t n = 0; n < POD_CLIENT_ID_MAX; n++) {
+ uint32_t id = (uint32_t)start + n;
+ if (id > POD_CLIENT_ID_MAX) {
+ id = id - POD_CLIENT_ID_MAX;
+ }
+ if (id < POD_CLIENT_ID_MIN) {
+ continue;
+ }
+ if (!id_in_use(id, NULL)) {
+ uint32_t next = id + 1;
+ if (next > POD_CLIENT_ID_MAX) {
+ next = POD_CLIENT_ID_MIN;
+ }
+ s_id_map.next_id = (uint8_t)next;
+ return id;
+ }
+ }
+ return 0;
+}
+
uint32_t client_registry_now_ms(void) {
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
@@ -95,7 +152,84 @@ static client_slot_t *alloc_slot(const uint8_t mac[CLIENT_MAC_LEN],
return NULL;
}
-void client_registry_init(void) { memset(s_clients, 0, sizeof(s_clients)); }
+void client_registry_init(void) {
+ memset(s_clients, 0, sizeof(s_clients));
+ memset(&s_id_map, 0, sizeof(s_id_map));
+ s_id_map.next_id = (uint8_t)POD_CLIENT_ID_MIN;
+ (void)pod_settings_load_id_map(&s_id_map);
+ if (s_id_map.next_id < POD_CLIENT_ID_MIN) {
+ s_id_map.next_id = (uint8_t)POD_CLIENT_ID_MIN;
+ }
+}
+
+esp_err_t client_registry_assign_id(const uint8_t mac[CLIENT_MAC_LEN],
+ uint32_t preferred_id, uint32_t *out_id) {
+ if (mac == NULL || out_id == NULL) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ int idx = find_map_index(mac);
+ if (idx >= 0) {
+ *out_id = s_id_map.entries[idx].id;
+ return ESP_OK;
+ }
+
+ uint32_t id = 0;
+ if (preferred_id >= POD_CLIENT_ID_MIN && preferred_id <= POD_CLIENT_ID_MAX &&
+ !id_in_use(preferred_id, mac)) {
+ id = preferred_id;
+ } else {
+ id = alloc_next_free_id();
+ }
+ if (id == 0) {
+ return ESP_ERR_NO_MEM;
+ }
+
+ if (s_id_map.count >= POD_ID_MAP_MAX) {
+ return ESP_ERR_NO_MEM;
+ }
+
+ memcpy(s_id_map.entries[s_id_map.count].mac, mac, CLIENT_MAC_LEN);
+ s_id_map.entries[s_id_map.count].id = (uint8_t)id;
+ s_id_map.count++;
+ if (id >= s_id_map.next_id) {
+ uint32_t next = id + 1;
+ if (next > POD_CLIENT_ID_MAX) {
+ next = POD_CLIENT_ID_MIN;
+ }
+ s_id_map.next_id = (uint8_t)next;
+ }
+
+ esp_err_t err = persist_id_map();
+ if (err != ESP_OK) {
+ ESP_LOGW(TAG, "persist id_map failed after assign id=%lu: %s",
+ (unsigned long)id, esp_err_to_name(err));
+ }
+ *out_id = id;
+ return ESP_OK;
+}
+
+void client_registry_clear_id_map(void) {
+ memset(s_clients, 0, sizeof(s_clients));
+ memset(&s_id_map, 0, sizeof(s_id_map));
+ s_id_map.next_id = (uint8_t)POD_CLIENT_ID_MIN;
+ esp_err_t err = pod_settings_clear_id_map();
+ if (err != ESP_OK) {
+ ESP_LOGW(TAG, "clear id_map NVS failed: %s", esp_err_to_name(err));
+ }
+ ESP_LOGI(TAG, "cleared id map and live registry");
+}
+
+void client_registry_mark_assign_sent(const uint8_t mac[CLIENT_MAC_LEN]) {
+ if (mac == NULL) {
+ return;
+ }
+ client_slot_t *slot = find_slot(mac);
+ if (slot == NULL) {
+ return;
+ }
+ slot->info.last_assign_sent_at = now_ms();
+}
const client_info_t *
client_registry_find_by_mac(const uint8_t mac[CLIENT_MAC_LEN]) {
@@ -171,18 +305,19 @@ void client_registry_check_timeouts(uint32_t timeout_ms) {
uint32_t now = now_ms();
for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) {
- if (!s_clients[i].active || !s_clients[i].info.available) {
+ if (!s_clients[i].active) {
continue;
}
uint32_t elapsed = now - s_clients[i].info.last_success_ping_at;
if (elapsed > timeout_ms) {
- s_clients[i].info.available = false;
char mac_str[18];
mac_to_str(s_clients[i].info.mac, mac_str, sizeof(mac_str));
- ESP_LOGW(TAG, "client inactive id=%lu mac=%s (no heartbeat for %lu ms)",
+ ESP_LOGW(TAG, "client dropped id=%lu mac=%s (no heartbeat for %lu ms)",
(unsigned long)s_clients[i].info.id, mac_str,
(unsigned long)elapsed);
+ s_clients[i].active = false;
+ memset(&s_clients[i].info, 0, sizeof(s_clients[i].info));
}
}
}
@@ -552,24 +687,15 @@ esp_err_t client_registry_update_battery(const uint8_t mac[CLIENT_MAC_LEN],
client_slot_t *slot = find_slot(mac);
if (slot == NULL) {
- bool is_new = false;
- esp_err_t err = client_registry_upsert(mac, slave_id, 0, true, false, &is_new);
- if (err != ESP_OK) {
- return err;
- }
- slot = find_slot(mac);
- if (slot == NULL) {
- return ESP_ERR_NOT_FOUND;
- }
- ESP_LOGI(TAG, "battery auto-registered id=%lu (report before heartbeat)",
- (unsigned long)slave_id);
+ /* Ids are assigned on presence only; ignore early battery reports. */
+ return ESP_ERR_NOT_FOUND;
}
- if (slot->info.id != slave_id) {
- ESP_LOGW(TAG, "battery id %lu → %lu for mac %02x:…:%02x",
+ if (slave_id != 0 && slot->info.id != slave_id) {
+ ESP_LOGW(TAG, "battery id mismatch slot=%lu report=%lu mac %02x:…:%02x",
(unsigned long)slot->info.id, (unsigned long)slave_id, mac[0],
mac[5]);
- slot->info.id = slave_id;
+ return ESP_ERR_INVALID_STATE;
}
slot->info.lipo1_valid = lipo1_valid;
diff --git a/main/client_registry.h b/main/client_registry.h
index fd804b9..3820353 100644
--- a/main/client_registry.h
+++ b/main/client_registry.h
@@ -47,6 +47,8 @@ typedef struct {
uint32_t lipo1_mv;
uint32_t lipo2_mv;
uint32_t battery_updated_at;
+ /** Master: last ESPNOW_ASSIGN_ID send time (rate-limit resends). */
+ uint32_t last_assign_sent_at;
} client_info_t;
#define CLIENT_REGISTRY_DEFAULT_ACCEL_DEADZONE 100u
@@ -55,6 +57,23 @@ typedef struct {
void client_registry_init(void);
+/**
+ * Assign a stable logical id for mac (1…255). Honors preferred if free or
+ * already owned by mac; otherwise allocates the next free id. Persists the
+ * master id map to NVS.
+ */
+esp_err_t client_registry_assign_id(const uint8_t mac[CLIENT_MAC_LEN],
+ uint32_t preferred_id, uint32_t *out_id);
+
+/**
+ * Clear NVS id map, RAM map, and all live registry slots. Does not notify
+ * slaves (caller sends CLEAR_ID_BINDING).
+ */
+void client_registry_clear_id_map(void);
+
+/** Record that ASSIGN_ID was sent to this MAC (for resend rate limiting). */
+void client_registry_mark_assign_sent(const uint8_t mac[CLIENT_MAC_LEN]);
+
/** Milliseconds since boot (same clock as stored ping timestamps). */
uint32_t client_registry_now_ms(void);
@@ -75,7 +94,10 @@ esp_err_t client_registry_heartbeat(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t id, uint32_t version, bool used,
bool *out_is_new, bool *out_reactivated);
-/** Mark clients inactive when last_success_ping_at is older than timeout_ms. */
+/**
+ * Drop live registry slots whose last_success_ping_at is older than
+ * timeout_ms (NVS id map unchanged). Offline clients disappear from CLIENT_INFO.
+ */
void client_registry_check_timeouts(uint32_t timeout_ms);
size_t client_registry_count(void);
diff --git a/main/cmd/cmd_client_id_map_reset.c b/main/cmd/cmd_client_id_map_reset.c
new file mode 100644
index 0000000..e67e711
--- /dev/null
+++ b/main/cmd/cmd_client_id_map_reset.c
@@ -0,0 +1,35 @@
+#include "cmd_client_id_map_reset.h"
+#include "esp_log.h"
+#include "esp_now_comm.h"
+#include "esp_now_core.h"
+#include "uart_cmd.h"
+
+static const char *TAG = "[ID_MAP_RESET]";
+
+static void reply(bool success) {
+ alox_UartMessage response;
+ uart_cmd_init_response(&response, alox_MessageType_CLIENT_ID_MAP_RESET,
+ alox_UartMessage_client_id_map_reset_response_tag);
+ response.payload.client_id_map_reset_response.success = success;
+ uart_cmd_send(&response, TAG);
+}
+
+static void handle_client_id_map_reset(const uint8_t *data, size_t len) {
+ (void)data;
+ (void)len;
+
+ esp_err_t err;
+ if (esp_now_core_is_master()) {
+ ESP_LOGI(TAG, "reset master id map + notify online slaves");
+ err = esp_now_comm_reset_client_id_map();
+ } else {
+ ESP_LOGI(TAG, "clear local slave id binding");
+ err = esp_now_comm_clear_local_id_binding();
+ }
+ reply(err == ESP_OK);
+}
+
+void cmd_client_id_map_reset_register(void) {
+ uart_cmd_register(alox_MessageType_CLIENT_ID_MAP_RESET,
+ handle_client_id_map_reset);
+}
diff --git a/main/cmd/cmd_client_id_map_reset.h b/main/cmd/cmd_client_id_map_reset.h
new file mode 100644
index 0000000..75b3a88
--- /dev/null
+++ b/main/cmd/cmd_client_id_map_reset.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void cmd_client_id_map_reset_register(void);
diff --git a/main/esp_now_comm.h b/main/esp_now_comm.h
index 663a313..a8abc5b 100644
--- a/main/esp_now_comm.h
+++ b/main/esp_now_comm.h
@@ -55,6 +55,22 @@ esp_err_t esp_now_comm_send_led_ring(const uint8_t mac[CLIENT_MAC_LEN],
esp_err_t esp_now_comm_send_restart(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t client_id);
+/** Master: tell slave its assigned logical client id. */
+esp_err_t esp_now_comm_send_assign_id(const uint8_t mac[CLIENT_MAC_LEN],
+ uint32_t assigned_id);
+
+/** Master: tell slave to forget persisted master/id binding. */
+esp_err_t esp_now_comm_send_clear_id_binding(const uint8_t mac[CLIENT_MAC_LEN]);
+
+/**
+ * Master: clear NVS id map + live registry and unicast CLEAR_ID_BINDING to
+ * every currently registered live slave (snapshot before clear).
+ */
+esp_err_t esp_now_comm_reset_client_id_map(void);
+
+/** Slave: clear local NVS master/id binding (UART reset on slave). */
+esp_err_t esp_now_comm_clear_local_id_binding(void);
+
/** Master → slave OTA (unicast). */
esp_err_t esp_now_comm_send_ota_start(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t total_size);
diff --git a/main/esp_now_master.c b/main/esp_now_master.c
index 51392ff..0ed8d98 100644
--- a/main/esp_now_master.c
+++ b/main/esp_now_master.c
@@ -22,6 +22,8 @@ static const uint8_t ESPNOW_BCAST[ESP_NOW_ETH_ALEN] = {0xff, 0xff, 0xff,
#define ESPNOW_CLIENT_TIMEOUT_MS \
(ESPNOW_HEARTBEAT_INTERVAL_MS * ESPNOW_HEARTBEAT_MISS_COUNT)
#define ESPNOW_BATTERY_INTERVAL_MS 30000
+/** Min gap between ASSIGN_ID resends when slave has not yet echoed the id. */
+#define ESPNOW_ASSIGN_RESEND_MS 2000u
static const char *TAG = "[ESPNOW_M]";
@@ -168,6 +170,22 @@ static esp_err_t send_restart(const uint8_t *dest_mac, uint32_t client_id) {
return esp_now_core_send(dest_mac, &msg);
}
+static esp_err_t send_assign_id(const uint8_t *dest_mac, uint32_t assigned_id) {
+ alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
+ msg.type = alox_EspNowMessageType_ESPNOW_ASSIGN_ID;
+ msg.which_payload = alox_EspNowMessage_assign_id_tag;
+ msg.payload.assign_id.assigned_id = assigned_id;
+ /* Fast path: avoid 8× NO_MEM retries blocking the shared send lock. */
+ return esp_now_core_send_fast(dest_mac, &msg);
+}
+
+static esp_err_t send_clear_id_binding(const uint8_t *dest_mac) {
+ alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
+ msg.type = alox_EspNowMessageType_ESPNOW_CLEAR_ID_BINDING;
+ msg.which_payload = alox_EspNowMessage_clear_id_binding_tag;
+ return esp_now_core_send_fast(dest_mac, &msg);
+}
+
static esp_err_t send_tap_notify(const uint8_t *dest_mac, uint32_t client_id,
bool single, bool double_tap, bool triple) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
@@ -251,6 +269,70 @@ esp_err_t esp_now_comm_send_restart(const uint8_t mac[CLIENT_MAC_LEN],
return err;
}
+esp_err_t esp_now_comm_send_assign_id(const uint8_t mac[CLIENT_MAC_LEN],
+ uint32_t assigned_id) {
+ if (mac == NULL || !esp_now_core_is_master()) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ char mac_str[18];
+ esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
+ esp_err_t err = send_assign_id(mac, assigned_id);
+ if (err == ESP_OK) {
+ ESP_LOGI(TAG, "unicast ASSIGN_ID %lu to %s", (unsigned long)assigned_id,
+ mac_str);
+ } else {
+ ESP_LOGW(TAG, "unicast ASSIGN_ID to %s failed: %s", mac_str,
+ esp_err_to_name(err));
+ }
+ return err;
+}
+
+esp_err_t esp_now_comm_send_clear_id_binding(const uint8_t mac[CLIENT_MAC_LEN]) {
+ if (mac == NULL || !esp_now_core_is_master()) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ char mac_str[18];
+ esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
+ esp_err_t err = send_clear_id_binding(mac);
+ if (err == ESP_OK) {
+ ESP_LOGI(TAG, "unicast CLEAR_ID_BINDING to %s", mac_str);
+ } else {
+ ESP_LOGW(TAG, "unicast CLEAR_ID_BINDING to %s failed: %s", mac_str,
+ esp_err_to_name(err));
+ }
+ return err;
+}
+
+esp_err_t esp_now_comm_reset_client_id_map(void) {
+ if (!esp_now_core_is_master()) {
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ uint8_t macs[CLIENT_REGISTRY_MAX][CLIENT_MAC_LEN];
+ size_t n = client_registry_count();
+ if (n > CLIENT_REGISTRY_MAX) {
+ n = CLIENT_REGISTRY_MAX;
+ }
+ size_t copied = 0;
+ for (size_t i = 0; i < n; i++) {
+ const client_info_t *c = client_registry_at(i);
+ if (c == NULL) {
+ continue;
+ }
+ memcpy(macs[copied], c->mac, CLIENT_MAC_LEN);
+ copied++;
+ }
+
+ client_registry_clear_id_map();
+
+ for (size_t i = 0; i < copied; i++) {
+ (void)esp_now_comm_send_clear_id_binding(macs[i]);
+ }
+ ESP_LOGI(TAG, "client id map reset; notified %u slave(s)", (unsigned)copied);
+ return ESP_OK;
+}
+
+
esp_err_t esp_now_comm_send_find_me(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t client_id) {
if (mac == NULL || !esp_now_core_is_master()) {
@@ -495,25 +577,53 @@ static void handle_client_presence(const alox_EspNowSlavePresence *presence,
esp_now_core_ensure_peer(mac);
+ uint32_t assigned_id = 0;
+ esp_err_t err =
+ client_registry_assign_id(mac, presence->slave_id, &assigned_id);
+ if (err != ESP_OK) {
+ ESP_LOGW(TAG, "id assign failed: %s", esp_err_to_name(err));
+ return;
+ }
+
bool is_new = false;
bool reactivated = false;
- esp_err_t err = client_registry_heartbeat(
- mac, presence->slave_id, presence->version, presence->used, &is_new,
- &reactivated);
+ err = client_registry_heartbeat(mac, assigned_id, presence->version,
+ presence->used, &is_new, &reactivated);
if (err != ESP_OK) {
ESP_LOGW(TAG, "client registry full");
return;
}
+ /*
+ * Send ASSIGN when new/rejoined, or when slave has not echoed the id yet.
+ * Rate-limit mismatch resends so failed assigns do not flood ESP-NOW buffers
+ * (ESP_ERR_ESPNOW_NO_MEM) and block other unicast traffic.
+ */
+ const bool needs_assign =
+ is_new || reactivated || presence->slave_id != assigned_id;
+ if (needs_assign) {
+ const client_info_t *client = client_registry_find_by_mac(mac);
+ const bool due =
+ is_new || reactivated || client == NULL ||
+ client->last_assign_sent_at == 0 ||
+ client_registry_ms_since(client->last_assign_sent_at) >=
+ ESPNOW_ASSIGN_RESEND_MS;
+ if (due) {
+ if (esp_now_comm_send_assign_id(mac, assigned_id) == ESP_OK) {
+ client_registry_mark_assign_sent(mac);
+ }
+ }
+ }
+
char mac_str[18];
esp_now_core_mac_to_str(mac, mac_str, sizeof(mac_str));
if (is_new) {
- ESP_LOGI(TAG, "client registered id=%lu mac=%s ver=%lu",
- (unsigned long)presence->slave_id, mac_str,
- (unsigned long)presence->version);
+ ESP_LOGI(TAG, "client registered id=%lu (preferred=%lu) mac=%s ver=%lu",
+ (unsigned long)assigned_id, (unsigned long)presence->slave_id,
+ mac_str, (unsigned long)presence->version);
} else if (reactivated) {
ESP_LOGI(TAG, "client reconnected id=%lu mac=%s",
- (unsigned long)presence->slave_id, mac_str);
+ (unsigned long)assigned_id, mac_str);
}
}
diff --git a/main/esp_now_slave.c b/main/esp_now_slave.c
index c8d3d07..d41a123 100644
--- a/main/esp_now_slave.c
+++ b/main/esp_now_slave.c
@@ -37,6 +37,11 @@ static bool s_tap_notify_double;
static bool s_tap_notify_triple;
static uint8_t s_master_mac[ESP_NOW_ETH_ALEN];
static uint32_t s_last_discover_ms;
+/** Logical id assigned by current/last master (0 = unbound). */
+static uint32_t s_assigned_id;
+/** Master MAC from NVS binding (may differ from current s_master_mac). */
+static uint8_t s_bound_master_mac[ESP_NOW_ETH_ALEN];
+static bool s_has_binding;
typedef enum {
SLAVE_TX_SLAVE_INFO = 1,
@@ -45,15 +50,91 @@ typedef enum {
static QueueHandle_t s_tx_queue;
+static void queue_tx(slave_tx_op_t op);
+
static bool from_joined_master(const uint8_t *master_mac) {
return s_joined && esp_now_core_mac_equal(master_mac, s_master_mac);
}
+static uint32_t my_client_id(void) { return s_assigned_id; }
+
+/** True when command targets this slave (0 = broadcast / any). */
+static bool client_id_matches(uint32_t client_id) {
+ if (client_id == 0) {
+ return true;
+ }
+ return s_assigned_id != 0 && client_id == s_assigned_id;
+}
+
+static uint32_t preferred_id_for_master(void) {
+ if (!s_has_binding || s_assigned_id == 0) {
+ return 0;
+ }
+ if (!s_joined || !esp_now_core_mac_equal(s_master_mac, s_bound_master_mac)) {
+ return 0;
+ }
+ return s_assigned_id;
+}
+
+static void load_id_binding(void) {
+ uint8_t mac[ESP_NOW_ETH_ALEN];
+ uint32_t id = 0;
+ s_has_binding = pod_settings_load_id_binding(mac, &id);
+ if (s_has_binding) {
+ memcpy(s_bound_master_mac, mac, ESP_NOW_ETH_ALEN);
+ s_assigned_id = id;
+ ESP_LOGI(TAG, "loaded id binding id=%lu master=%02x:…:%02x",
+ (unsigned long)id, mac[0], mac[5]);
+ } else {
+ memset(s_bound_master_mac, 0, sizeof(s_bound_master_mac));
+ s_assigned_id = 0;
+ }
+}
+
+static void apply_assigned_id(uint32_t assigned_id) {
+ if (assigned_id < POD_CLIENT_ID_MIN || assigned_id > POD_CLIENT_ID_MAX) {
+ ESP_LOGW(TAG, "ignore invalid assigned_id=%lu", (unsigned long)assigned_id);
+ return;
+ }
+ const bool same =
+ s_has_binding && s_assigned_id == assigned_id &&
+ esp_now_core_mac_equal(s_bound_master_mac, s_master_mac);
+ s_assigned_id = assigned_id;
+ memcpy(s_bound_master_mac, s_master_mac, ESP_NOW_ETH_ALEN);
+ s_has_binding = true;
+ if (same) {
+ return;
+ }
+ if (pod_settings_save_id_binding(s_master_mac, assigned_id) != ESP_OK) {
+ ESP_LOGW(TAG, "assigned id=%lu but NVS save failed",
+ (unsigned long)assigned_id);
+ } else {
+ ESP_LOGI(TAG, "assigned id=%lu (persisted)", (unsigned long)assigned_id);
+ }
+ queue_tx(SLAVE_TX_BATTERY);
+}
+
+static void clear_id_binding_local(void) {
+ s_assigned_id = 0;
+ s_has_binding = false;
+ memset(s_bound_master_mac, 0, sizeof(s_bound_master_mac));
+ (void)pod_settings_clear_id_binding();
+ ESP_LOGI(TAG, "cleared local id binding");
+}
+
+esp_err_t esp_now_comm_clear_local_id_binding(void) {
+ if (esp_now_core_is_master()) {
+ return ESP_OK;
+ }
+ clear_id_binding_local();
+ return ESP_OK;
+}
+
static void fill_presence(alox_EspNowSlavePresence *presence) {
const uint8_t *own = esp_now_core_own_mac();
presence->network = esp_now_core_network();
presence->version = POWERPOD_FW_VERSION;
- presence->slave_id = own[5];
+ presence->slave_id = preferred_id_for_master();
presence->available = true;
presence->used = false;
esp_now_proto_setup_presence_encode(presence, own);
@@ -139,7 +220,7 @@ static void queue_tx(slave_tx_op_t op) {
}
static void send_battery_to_master(void) {
- if (!s_joined) {
+ if (!s_joined || s_assigned_id == 0) {
return;
}
@@ -147,7 +228,7 @@ static void send_battery_to_master(void) {
board_input_read_lipo(&reading);
alox_EspNowBatteryReport report = alox_EspNowBatteryReport_init_zero;
- report.client_id = esp_now_core_own_mac()[5];
+ report.client_id = s_assigned_id;
report.lipo1_valid = reading.lipo1_valid;
report.lipo2_valid = reading.lipo2_valid;
report.lipo1_mv = reading.lipo1_mv;
@@ -261,10 +342,7 @@ static void handle_echo_ping(const uint8_t *master_mac,
static void handle_restart(const uint8_t *master_mac,
const alox_EspNowRestart *req) {
- const uint8_t *own = esp_now_core_own_mac();
- uint32_t my_id = own[5];
-
- if (req->client_id != 0 && req->client_id != my_id) {
+ if (!client_id_matches(req->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -273,14 +351,14 @@ static void handle_restart(const uint8_t *master_mac,
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
- ESP_LOGI(TAG, "RESTART from master %s (id=%lu)", mac_str, (unsigned long)my_id);
+ ESP_LOGI(TAG, "RESTART from master %s (id=%lu)", mac_str,
+ (unsigned long)my_client_id());
pod_schedule_restart();
}
static void handle_battery_query(const uint8_t *master_mac,
const alox_EspNowBatteryQuery *query) {
- uint32_t my_id = esp_now_core_own_mac()[5];
- if (query->client_id != 0 && query->client_id != my_id) {
+ if (!client_id_matches(query->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -291,8 +369,7 @@ static void handle_battery_query(const uint8_t *master_mac,
static void handle_led_ring(const uint8_t *master_mac,
const alox_EspNowLedRing *msg) {
- uint32_t my_id = esp_now_core_own_mac()[5];
- if (msg->client_id != 0 && msg->client_id != my_id) {
+ if (!client_id_matches(msg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -313,13 +390,12 @@ static void handle_led_ring(const uint8_t *master_mac,
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "LED_RING mode %lu from master %s (id=%lu)",
- (unsigned long)req.mode, mac_str, (unsigned long)my_id);
+ (unsigned long)req.mode, mac_str, (unsigned long)my_client_id());
cmd_led_ring_apply(&req);
}
static void handle_find_me(const uint8_t *master_mac, const alox_EspNowFindMe *req) {
- uint32_t my_id = esp_now_core_own_mac()[5];
- if (req->client_id != 0 && req->client_id != my_id) {
+ if (!client_id_matches(req->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -327,14 +403,14 @@ static void handle_find_me(const uint8_t *master_mac, const alox_EspNowFindMe *r
}
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
- ESP_LOGI(TAG, "FIND_ME from master %s (id=%lu)", mac_str, (unsigned long)my_id);
+ ESP_LOGI(TAG, "FIND_ME from master %s (id=%lu)", mac_str,
+ (unsigned long)my_client_id());
led_ring_find_me();
}
static void handle_accel_stream(const uint8_t *master_mac,
const alox_EspNowAccelStream *cfg) {
- uint32_t my_id = esp_now_core_own_mac()[5];
- if (cfg->client_id != 0 && cfg->client_id != my_id) {
+ if (!client_id_matches(cfg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -344,13 +420,12 @@ static void handle_accel_stream(const uint8_t *master_mac,
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "accel stream %s from master %s (id=%lu)",
- cfg->enable ? "on" : "off", mac_str, (unsigned long)my_id);
+ cfg->enable ? "on" : "off", mac_str, (unsigned long)my_client_id());
}
static void handle_tap_notify(const uint8_t *master_mac,
const alox_EspNowTapNotify *cfg) {
- uint32_t my_id = esp_now_core_own_mac()[5];
- if (cfg->client_id != 0 && cfg->client_id != my_id) {
+ if (!client_id_matches(cfg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -364,13 +439,12 @@ static void handle_tap_notify(const uint8_t *master_mac,
ESP_LOGI(TAG,
"tap notify single=%d double=%d triple=%d from master %s (id=%lu)",
cfg->single, cfg->double_tap, cfg->triple, mac_str,
- (unsigned long)my_id);
+ (unsigned long)my_client_id());
}
static void handle_accel_deadzone(const uint8_t *master_mac,
const alox_EspNowAccelDeadzone *cfg) {
- uint32_t my_id = esp_now_core_own_mac()[5];
- if (cfg->client_id != 0 && cfg->client_id != my_id) {
+ if (!client_id_matches(cfg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -380,7 +454,7 @@ static void handle_accel_deadzone(const uint8_t *master_mac,
esp_now_core_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,
+ (unsigned long)cfg->deadzone, (unsigned long)my_client_id(),
bma456_is_ready() ? "ok" : "not installed");
bma456_set_accel_deadzone(cfg->deadzone);
if (pod_settings_save_accel_deadzone(cfg->deadzone) != ESP_OK) {
@@ -391,8 +465,7 @@ static void handle_accel_deadzone(const uint8_t *master_mac,
static void handle_tap_config(const uint8_t *master_mac,
const alox_EspNowTapConfig *msg) {
- uint32_t my_id = esp_now_core_own_mac()[5];
- if (msg->client_id != 0 && msg->client_id != my_id) {
+ if (!client_id_matches(msg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -415,7 +488,7 @@ static void handle_tap_config(const uint8_t *master_mac,
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG,
"tap config from master %s: sens=%u id=%lu (sensor %s)", mac_str,
- (unsigned)cfg.tap_sens_thres, (unsigned long)my_id,
+ (unsigned)cfg.tap_sens_thres, (unsigned long)my_client_id(),
bma456_is_ready() ? "ok" : "not installed");
if (!bma456_tap_config_valid(&cfg)) {
@@ -431,6 +504,21 @@ static void handle_tap_config(const uint8_t *master_mac,
}
}
+static void handle_assign_id(const uint8_t *master_mac,
+ const alox_EspNowAssignId *msg) {
+ if (!from_joined_master(master_mac)) {
+ return;
+ }
+ apply_assigned_id(msg->assigned_id);
+}
+
+static void handle_clear_id_binding(const uint8_t *master_mac) {
+ if (!from_joined_master(master_mac)) {
+ return;
+ }
+ clear_id_binding_local();
+}
+
static void handle_discover(const uint8_t *sender_mac,
const alox_EspNowDiscover *discover) {
if (discover->network != esp_now_core_network()) {
@@ -464,11 +552,12 @@ static void handle_discover(const uint8_t *sender_mac,
char mac_str[18];
esp_now_core_mac_to_str(sender_mac, mac_str, sizeof(mac_str));
- ESP_LOGI(TAG, "joined network %u, master %s", (unsigned)discover->network,
- mac_str);
+ ESP_LOGI(TAG, "joined network %u, master %s (preferred_id=%lu)",
+ (unsigned)discover->network, mac_str,
+ (unsigned long)preferred_id_for_master());
queue_tx(SLAVE_TX_SLAVE_INFO);
- queue_tx(SLAVE_TX_BATTERY);
+ /* Battery after ASSIGN_ID so reports carry the definitive client_id. */
}
static void check_master_timeout(void) {
@@ -489,14 +578,14 @@ static void check_master_timeout(void) {
static void accel_stream_task(void *param) {
(void)param;
- const uint8_t *own = esp_now_core_own_mac();
ESP_LOGI(TAG, "accel stream task (interval %u ms)",
(unsigned)ESPNOW_ACCEL_INTERVAL_MS);
while (1) {
vTaskDelay(pdMS_TO_TICKS(ESPNOW_ACCEL_INTERVAL_MS));
- if (!s_joined || !s_accel_stream_enabled || !bma456_is_ready()) {
+ if (!s_joined || s_assigned_id == 0 || !s_accel_stream_enabled ||
+ !bma456_is_ready()) {
continue;
}
int16_t x = 0;
@@ -505,13 +594,13 @@ static void accel_stream_task(void *param) {
if (bma456_read_accel(&x, &y, &z) != ESP_OK) {
continue;
}
- (void)send_accel_sample(s_master_mac, own[5], x, y, z);
+ (void)send_accel_sample(s_master_mac, s_assigned_id, x, y, z);
}
}
static void on_bma456_tap(bma456_tap_kind_t kind, void *ctx) {
(void)ctx;
- if (!s_joined) {
+ if (!s_joined || s_assigned_id == 0) {
return;
}
bool enabled = false;
@@ -531,7 +620,7 @@ static void on_bma456_tap(bma456_tap_kind_t kind, void *ctx) {
if (!enabled) {
return;
}
- (void)send_tap_event(s_master_mac, esp_now_core_own_mac()[5], (uint32_t)kind);
+ (void)send_tap_event(s_master_mac, s_assigned_id, (uint32_t)kind);
}
static void heartbeat_task(void *param) {
@@ -654,6 +743,12 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
handle_restart(info->src_addr, &msg.payload.restart);
}
break;
+ case alox_EspNowMessage_assign_id_tag:
+ handle_assign_id(info->src_addr, &msg.payload.assign_id);
+ break;
+ case alox_EspNowMessage_clear_id_binding_tag:
+ handle_clear_id_binding(info->src_addr);
+ break;
case alox_EspNowMessage_ota_start_tag:
if (from_joined_master(info->src_addr)) {
ota_espnow_slave_on_start(info->src_addr, &msg.payload.ota_start);
@@ -668,6 +763,7 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
esp_err_t esp_now_slave_start(void) {
reset_join();
+ load_id_binding();
s_tx_queue = xQueueCreate(4, sizeof(slave_tx_op_t));
if (s_tx_queue == NULL) {
diff --git a/main/pod_settings.c b/main/pod_settings.c
index a2b23e6..2faa7c4 100644
--- a/main/pod_settings.c
+++ b/main/pod_settings.c
@@ -10,9 +10,31 @@ static const char *NS = "powerpod";
static const char *KEY_ACCEL_DZ = "accel_dz";
static const char *KEY_UV_LATCH = "uv_latch";
static const char *KEY_TAP_CFG = "tap_cfg";
+static const char *KEY_ID_MAP = "id_map";
+static const char *KEY_ID_BIND = "id_bind";
#define ACCEL_DEADZONE_MAX 4095u
#define TAP_CFG_NVS_VERSION 1u
+#define ID_MAP_NVS_VERSION 1u
+#define ID_BIND_NVS_VERSION 1u
+
+typedef struct __attribute__((packed)) {
+ uint8_t mac[POD_ID_MAC_LEN];
+ uint8_t id;
+} id_map_entry_nvs_t;
+
+typedef struct __attribute__((packed)) {
+ uint8_t version;
+ uint8_t next_id;
+ uint8_t count;
+ id_map_entry_nvs_t entries[POD_ID_MAP_MAX];
+} id_map_nvs_t;
+
+typedef struct __attribute__((packed)) {
+ uint8_t version;
+ uint8_t master_mac[POD_ID_MAC_LEN];
+ uint8_t assigned_id;
+} id_bind_nvs_t;
/** Packed layout for NVS blob (stable across rebuilds). */
typedef struct __attribute__((packed)) {
@@ -302,3 +324,232 @@ esp_err_t pod_settings_set_uv_latched(bool latched) {
esp_err_t pod_settings_clear_uv_latched(void) {
return pod_settings_set_uv_latched(false);
}
+
+esp_err_t pod_settings_load_id_map(pod_id_map_t *out) {
+ if (out == NULL) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ memset(out, 0, sizeof(*out));
+ out->next_id = (uint8_t)POD_CLIENT_ID_MIN;
+ if (!s_nvs_ready) {
+ return ESP_OK;
+ }
+
+ nvs_handle_t handle;
+ esp_err_t err = nvs_open(NS, NVS_READONLY, &handle);
+ if (err != ESP_OK) {
+ return ESP_OK;
+ }
+
+ id_map_nvs_t blob;
+ memset(&blob, 0, sizeof(blob));
+ size_t len = sizeof(blob);
+ err = nvs_get_blob(handle, KEY_ID_MAP, &blob, &len);
+ nvs_close(handle);
+
+ if (err == ESP_ERR_NVS_NOT_FOUND) {
+ return ESP_OK;
+ }
+ if (err != ESP_OK || len < 3 || blob.version != ID_MAP_NVS_VERSION) {
+ ESP_LOGW(TAG, "read id_map failed: %s (len=%u)", esp_err_to_name(err),
+ (unsigned)len);
+ return ESP_OK;
+ }
+ if (blob.count > POD_ID_MAP_MAX) {
+ ESP_LOGW(TAG, "id_map count %u invalid", (unsigned)blob.count);
+ return ESP_OK;
+ }
+ if (blob.next_id < POD_CLIENT_ID_MIN) {
+ blob.next_id = (uint8_t)POD_CLIENT_ID_MIN;
+ }
+
+ out->next_id = blob.next_id;
+ out->count = blob.count;
+ for (uint8_t i = 0; i < blob.count; i++) {
+ memcpy(out->entries[i].mac, blob.entries[i].mac, POD_ID_MAC_LEN);
+ out->entries[i].id = blob.entries[i].id;
+ }
+ ESP_LOGI(TAG, "loaded id_map count=%u next_id=%u", (unsigned)out->count,
+ (unsigned)out->next_id);
+ return ESP_OK;
+}
+
+esp_err_t pod_settings_save_id_map(const pod_id_map_t *map) {
+ if (map == NULL || map->count > POD_ID_MAP_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ if (map->next_id < POD_CLIENT_ID_MIN) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ esp_err_t err = ensure_nvs();
+ if (err != ESP_OK) {
+ return err;
+ }
+
+ id_map_nvs_t blob;
+ memset(&blob, 0, sizeof(blob));
+ blob.version = ID_MAP_NVS_VERSION;
+ blob.next_id = map->next_id;
+ blob.count = map->count;
+ for (uint8_t i = 0; i < map->count; i++) {
+ memcpy(blob.entries[i].mac, map->entries[i].mac, POD_ID_MAC_LEN);
+ blob.entries[i].id = map->entries[i].id;
+ }
+
+ nvs_handle_t handle;
+ err = nvs_open(NS, NVS_READWRITE, &handle);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
+ return err;
+ }
+
+ err = nvs_set_blob(handle, KEY_ID_MAP, &blob, sizeof(blob));
+ if (err == ESP_OK) {
+ err = nvs_commit(handle);
+ }
+ nvs_close(handle);
+
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "save id_map failed: %s", esp_err_to_name(err));
+ return err;
+ }
+ ESP_LOGI(TAG, "saved id_map count=%u next_id=%u", (unsigned)map->count,
+ (unsigned)map->next_id);
+ return ESP_OK;
+}
+
+esp_err_t pod_settings_clear_id_map(void) {
+ esp_err_t err = ensure_nvs();
+ if (err != ESP_OK) {
+ return err;
+ }
+
+ nvs_handle_t handle;
+ err = nvs_open(NS, NVS_READWRITE, &handle);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
+ return err;
+ }
+
+ err = nvs_erase_key(handle, KEY_ID_MAP);
+ if (err == ESP_ERR_NVS_NOT_FOUND) {
+ err = ESP_OK;
+ }
+ if (err == ESP_OK) {
+ err = nvs_commit(handle);
+ }
+ nvs_close(handle);
+
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "clear id_map failed: %s", esp_err_to_name(err));
+ return err;
+ }
+ ESP_LOGI(TAG, "cleared id_map");
+ return ESP_OK;
+}
+
+bool pod_settings_load_id_binding(uint8_t master_mac[POD_ID_MAC_LEN],
+ uint32_t *assigned_id_out) {
+ if (master_mac == NULL || assigned_id_out == NULL) {
+ return false;
+ }
+ memset(master_mac, 0, POD_ID_MAC_LEN);
+ *assigned_id_out = 0;
+ if (!s_nvs_ready) {
+ return false;
+ }
+
+ nvs_handle_t handle;
+ esp_err_t err = nvs_open(NS, NVS_READONLY, &handle);
+ if (err != ESP_OK) {
+ return false;
+ }
+
+ id_bind_nvs_t blob = {0};
+ size_t len = sizeof(blob);
+ err = nvs_get_blob(handle, KEY_ID_BIND, &blob, &len);
+ nvs_close(handle);
+
+ if (err != ESP_OK || len != sizeof(blob) ||
+ blob.version != ID_BIND_NVS_VERSION) {
+ return false;
+ }
+ if (blob.assigned_id < POD_CLIENT_ID_MIN) {
+ return false;
+ }
+
+ memcpy(master_mac, blob.master_mac, POD_ID_MAC_LEN);
+ *assigned_id_out = blob.assigned_id;
+ return true;
+}
+
+esp_err_t pod_settings_save_id_binding(const uint8_t master_mac[POD_ID_MAC_LEN],
+ uint32_t assigned_id) {
+ if (master_mac == NULL || assigned_id < POD_CLIENT_ID_MIN ||
+ assigned_id > POD_CLIENT_ID_MAX) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ esp_err_t err = ensure_nvs();
+ if (err != ESP_OK) {
+ return err;
+ }
+
+ id_bind_nvs_t blob = {
+ .version = ID_BIND_NVS_VERSION,
+ .assigned_id = (uint8_t)assigned_id,
+ };
+ memcpy(blob.master_mac, master_mac, POD_ID_MAC_LEN);
+
+ nvs_handle_t handle;
+ err = nvs_open(NS, NVS_READWRITE, &handle);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
+ return err;
+ }
+
+ err = nvs_set_blob(handle, KEY_ID_BIND, &blob, sizeof(blob));
+ if (err == ESP_OK) {
+ err = nvs_commit(handle);
+ }
+ nvs_close(handle);
+
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "save id_bind failed: %s", esp_err_to_name(err));
+ return err;
+ }
+ ESP_LOGI(TAG, "saved id_bind id=%lu master=%02x:…:%02x",
+ (unsigned long)assigned_id, master_mac[0], master_mac[5]);
+ return ESP_OK;
+}
+
+esp_err_t pod_settings_clear_id_binding(void) {
+ esp_err_t err = ensure_nvs();
+ if (err != ESP_OK) {
+ return err;
+ }
+
+ nvs_handle_t handle;
+ err = nvs_open(NS, NVS_READWRITE, &handle);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
+ return err;
+ }
+
+ err = nvs_erase_key(handle, KEY_ID_BIND);
+ if (err == ESP_ERR_NVS_NOT_FOUND) {
+ err = ESP_OK;
+ }
+ if (err == ESP_OK) {
+ err = nvs_commit(handle);
+ }
+ nvs_close(handle);
+
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "clear id_bind failed: %s", esp_err_to_name(err));
+ return err;
+ }
+ ESP_LOGI(TAG, "cleared id_bind");
+ return ESP_OK;
+}
diff --git a/main/pod_settings.h b/main/pod_settings.h
index f472cb2..46fcdbc 100644
--- a/main/pod_settings.h
+++ b/main/pod_settings.h
@@ -35,4 +35,36 @@ bool pod_settings_is_uv_latched(void);
esp_err_t pod_settings_set_uv_latched(bool latched);
esp_err_t pod_settings_clear_uv_latched(void);
+/** Logical slave ids: 1…POD_CLIENT_ID_MAX (0 reserved for local master). */
+#define POD_CLIENT_ID_MIN 1u
+#define POD_CLIENT_ID_MAX 255u
+#define POD_ID_MAP_MAX 255u
+#define POD_ID_MAC_LEN 6u
+
+typedef struct {
+ uint8_t mac[POD_ID_MAC_LEN];
+ uint8_t id;
+} pod_id_map_entry_t;
+
+typedef struct {
+ uint8_t next_id; /**< Next candidate when allocating (1…255). */
+ uint8_t count;
+ pod_id_map_entry_t entries[POD_ID_MAP_MAX];
+} pod_id_map_t;
+
+/** Master: load MAC→id map from NVS (zeros *out if unset). */
+esp_err_t pod_settings_load_id_map(pod_id_map_t *out);
+esp_err_t pod_settings_save_id_map(const pod_id_map_t *map);
+esp_err_t pod_settings_clear_id_map(void);
+
+/**
+ * Slave: load persisted {master_mac, assigned_id}.
+ * Returns true when a valid binding was loaded.
+ */
+bool pod_settings_load_id_binding(uint8_t master_mac[POD_ID_MAC_LEN],
+ uint32_t *assigned_id_out);
+esp_err_t pod_settings_save_id_binding(const uint8_t master_mac[POD_ID_MAC_LEN],
+ uint32_t assigned_id);
+esp_err_t pod_settings_clear_id_binding(void);
+
#endif
diff --git a/main/powerpod.c b/main/powerpod.c
index 32cd55c..8c04378 100644
--- a/main/powerpod.c
+++ b/main/powerpod.c
@@ -9,6 +9,7 @@
#include "cmd_espnow_echo_ping.h"
#include "cmd_espnow_find_me.h"
#include "cmd_restart.h"
+#include "cmd_client_id_map_reset.h"
#include "cmd_client_info.h"
#include "cmd_version.h"
#include "cmd_ota.h"
@@ -226,6 +227,7 @@ void app_main(void) {
cmd_espnow_echo_ping_register();
cmd_espnow_find_me_register();
cmd_restart_register();
+ cmd_client_id_map_reset_register();
cmd_led_ring_register();
cmd_battery_register();
cmd_ota_register();
diff --git a/main/proto/esp_now_messages.pb.c b/main/proto/esp_now_messages.pb.c
index 9f767b5..4b278c7 100644
--- a/main/proto/esp_now_messages.pb.c
+++ b/main/proto/esp_now_messages.pb.c
@@ -27,6 +27,12 @@ PB_BIND(alox_EspNowDiscover, alox_EspNowDiscover, AUTO)
PB_BIND(alox_EspNowSlavePresence, alox_EspNowSlavePresence, AUTO)
+PB_BIND(alox_EspNowAssignId, alox_EspNowAssignId, AUTO)
+
+
+PB_BIND(alox_EspNowClearIdBinding, alox_EspNowClearIdBinding, AUTO)
+
+
PB_BIND(alox_EspNowAccelDeadzone, alox_EspNowAccelDeadzone, AUTO)
diff --git a/main/proto/esp_now_messages.pb.h b/main/proto/esp_now_messages.pb.h
index cafaab3..6788646 100644
--- a/main/proto/esp_now_messages.pb.h
+++ b/main/proto/esp_now_messages.pb.h
@@ -32,7 +32,11 @@ typedef enum _alox_EspNowMessageType {
alox_EspNowMessageType_ESPNOW_TAP_EVENT = 18,
alox_EspNowMessageType_ESPNOW_ECHO_PING = 19,
alox_EspNowMessageType_ESPNOW_ECHO_PONG = 20,
- alox_EspNowMessageType_ESPNOW_SET_TAP_CONFIG = 21
+ alox_EspNowMessageType_ESPNOW_SET_TAP_CONFIG = 21,
+ /* * Master → slave: assigned logical client id after join/presence. */
+ alox_EspNowMessageType_ESPNOW_ASSIGN_ID = 22,
+ /* * Master → slave: forget persisted master/id binding (debug reset). */
+ alox_EspNowMessageType_ESPNOW_CLEAR_ID_BINDING = 23
} alox_EspNowMessageType;
/* Struct definitions */
@@ -74,11 +78,22 @@ typedef struct _alox_EspNowSlavePresence {
uint32_t network;
pb_callback_t mac;
uint32_t version;
+ /* * Preferred id from NVS for this master (0 = none / unbound). */
uint32_t slave_id;
bool available;
bool used;
} alox_EspNowSlavePresence;
+/* * Master → slave: definitive logical client id for this session/master. */
+typedef struct _alox_EspNowAssignId {
+ uint32_t assigned_id;
+} alox_EspNowAssignId;
+
+/* * Master → slave: clear persisted {master_mac, assigned_id} binding. */
+typedef struct _alox_EspNowClearIdBinding {
+ char dummy_field;
+} alox_EspNowClearIdBinding;
+
typedef struct _alox_EspNowAccelDeadzone {
uint32_t deadzone;
uint32_t client_id; /* 0 = all slaves; otherwise only matching slave_id applies */
@@ -204,6 +219,8 @@ typedef struct _alox_EspNowMessage {
alox_EspNowEchoPing echo_ping;
alox_EspNowEchoPong echo_pong;
alox_EspNowTapConfig tap_config;
+ alox_EspNowAssignId assign_id;
+ alox_EspNowClearIdBinding clear_id_binding;
} payload;
} alox_EspNowMessage;
@@ -214,8 +231,10 @@ extern "C" {
/* Helper constants for enums */
#define _alox_EspNowMessageType_MIN alox_EspNowMessageType_ESPNOW_UNKNOWN
-#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_SET_TAP_CONFIG
-#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_SET_TAP_CONFIG+1))
+#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_CLEAR_ID_BINDING
+#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_CLEAR_ID_BINDING+1))
+
+
@@ -248,6 +267,8 @@ extern "C" {
#define alox_EspNowRestart_init_default {0}
#define alox_EspNowDiscover_init_default {0, 0}
#define alox_EspNowSlavePresence_init_default {0, {{NULL}, NULL}, 0, 0, 0, 0}
+#define alox_EspNowAssignId_init_default {0}
+#define alox_EspNowClearIdBinding_init_default {0}
#define alox_EspNowAccelDeadzone_init_default {0, 0}
#define alox_EspNowAccelStream_init_default {0, 0}
#define alox_EspNowAccelSample_init_default {0, 0, 0, 0}
@@ -269,6 +290,8 @@ extern "C" {
#define alox_EspNowRestart_init_zero {0}
#define alox_EspNowDiscover_init_zero {0, 0}
#define alox_EspNowSlavePresence_init_zero {0, {{NULL}, NULL}, 0, 0, 0, 0}
+#define alox_EspNowAssignId_init_zero {0}
+#define alox_EspNowClearIdBinding_init_zero {0}
#define alox_EspNowAccelDeadzone_init_zero {0, 0}
#define alox_EspNowAccelStream_init_zero {0, 0}
#define alox_EspNowAccelSample_init_zero {0, 0, 0, 0}
@@ -300,6 +323,7 @@ extern "C" {
#define alox_EspNowSlavePresence_slave_id_tag 4
#define alox_EspNowSlavePresence_available_tag 5
#define alox_EspNowSlavePresence_used_tag 6
+#define alox_EspNowAssignId_assigned_id_tag 1
#define alox_EspNowAccelDeadzone_deadzone_tag 1
#define alox_EspNowAccelDeadzone_client_id_tag 2
#define alox_EspNowAccelStream_enable_tag 1
@@ -368,6 +392,8 @@ extern "C" {
#define alox_EspNowMessage_echo_ping_tag 20
#define alox_EspNowMessage_echo_pong_tag 21
#define alox_EspNowMessage_tap_config_tag 22
+#define alox_EspNowMessage_assign_id_tag 23
+#define alox_EspNowMessage_clear_id_binding_tag 24
/* Struct field encoding specification for nanopb */
#define alox_EspNowUnicastTest_FIELDLIST(X, a) \
@@ -413,6 +439,16 @@ X(a, STATIC, SINGULAR, BOOL, used, 6)
#define alox_EspNowSlavePresence_CALLBACK pb_default_field_callback
#define alox_EspNowSlavePresence_DEFAULT NULL
+#define alox_EspNowAssignId_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, assigned_id, 1)
+#define alox_EspNowAssignId_CALLBACK NULL
+#define alox_EspNowAssignId_DEFAULT NULL
+
+#define alox_EspNowClearIdBinding_FIELDLIST(X, a) \
+
+#define alox_EspNowClearIdBinding_CALLBACK NULL
+#define alox_EspNowClearIdBinding_DEFAULT NULL
+
#define alox_EspNowAccelDeadzone_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, deadzone, 1) \
X(a, STATIC, SINGULAR, UINT32, client_id, 2)
@@ -534,7 +570,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify,payload.tap_notify), 18)
X(a, STATIC, ONEOF, MESSAGE, (payload,tap_event,payload.tap_event), 19) \
X(a, STATIC, ONEOF, MESSAGE, (payload,echo_ping,payload.echo_ping), 20) \
X(a, STATIC, ONEOF, MESSAGE, (payload,echo_pong,payload.echo_pong), 21) \
-X(a, STATIC, ONEOF, MESSAGE, (payload,tap_config,payload.tap_config), 22)
+X(a, STATIC, ONEOF, MESSAGE, (payload,tap_config,payload.tap_config), 22) \
+X(a, STATIC, ONEOF, MESSAGE, (payload,assign_id,payload.assign_id), 23) \
+X(a, STATIC, ONEOF, MESSAGE, (payload,clear_id_binding,payload.clear_id_binding), 24)
#define alox_EspNowMessage_CALLBACK NULL
#define alox_EspNowMessage_DEFAULT NULL
#define alox_EspNowMessage_payload_discover_MSGTYPE alox_EspNowDiscover
@@ -558,6 +596,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_config,payload.tap_config), 22)
#define alox_EspNowMessage_payload_echo_ping_MSGTYPE alox_EspNowEchoPing
#define alox_EspNowMessage_payload_echo_pong_MSGTYPE alox_EspNowEchoPong
#define alox_EspNowMessage_payload_tap_config_MSGTYPE alox_EspNowTapConfig
+#define alox_EspNowMessage_payload_assign_id_MSGTYPE alox_EspNowAssignId
+#define alox_EspNowMessage_payload_clear_id_binding_MSGTYPE alox_EspNowClearIdBinding
extern const pb_msgdesc_t alox_EspNowUnicastTest_msg;
extern const pb_msgdesc_t alox_EspNowEchoPing_msg;
@@ -566,6 +606,8 @@ extern const pb_msgdesc_t alox_EspNowFindMe_msg;
extern const pb_msgdesc_t alox_EspNowRestart_msg;
extern const pb_msgdesc_t alox_EspNowDiscover_msg;
extern const pb_msgdesc_t alox_EspNowSlavePresence_msg;
+extern const pb_msgdesc_t alox_EspNowAssignId_msg;
+extern const pb_msgdesc_t alox_EspNowClearIdBinding_msg;
extern const pb_msgdesc_t alox_EspNowAccelDeadzone_msg;
extern const pb_msgdesc_t alox_EspNowAccelStream_msg;
extern const pb_msgdesc_t alox_EspNowAccelSample_msg;
@@ -589,6 +631,8 @@ extern const pb_msgdesc_t alox_EspNowMessage_msg;
#define alox_EspNowRestart_fields &alox_EspNowRestart_msg
#define alox_EspNowDiscover_fields &alox_EspNowDiscover_msg
#define alox_EspNowSlavePresence_fields &alox_EspNowSlavePresence_msg
+#define alox_EspNowAssignId_fields &alox_EspNowAssignId_msg
+#define alox_EspNowClearIdBinding_fields &alox_EspNowClearIdBinding_msg
#define alox_EspNowAccelDeadzone_fields &alox_EspNowAccelDeadzone_msg
#define alox_EspNowAccelStream_fields &alox_EspNowAccelStream_msg
#define alox_EspNowAccelSample_fields &alox_EspNowAccelSample_msg
@@ -611,8 +655,10 @@ extern const pb_msgdesc_t alox_EspNowMessage_msg;
#define alox_EspNowAccelDeadzone_size 12
#define alox_EspNowAccelSample_size 24
#define alox_EspNowAccelStream_size 8
+#define alox_EspNowAssignId_size 6
#define alox_EspNowBatteryQuery_size 6
#define alox_EspNowBatteryReport_size 22
+#define alox_EspNowClearIdBinding_size 0
#define alox_EspNowDiscover_size 8
#define alox_EspNowEchoPing_size 22
#define alox_EspNowEchoPong_size 22
diff --git a/main/proto/esp_now_messages.proto b/main/proto/esp_now_messages.proto
index 593dccf..c70f137 100644
--- a/main/proto/esp_now_messages.proto
+++ b/main/proto/esp_now_messages.proto
@@ -27,6 +27,10 @@ enum EspNowMessageType {
ESPNOW_ECHO_PING = 19;
ESPNOW_ECHO_PONG = 20;
ESPNOW_SET_TAP_CONFIG = 21;
+ /** Master → slave: assigned logical client id after join/presence. */
+ ESPNOW_ASSIGN_ID = 22;
+ /** Master → slave: forget persisted master/id binding (debug reset). */
+ ESPNOW_CLEAR_ID_BINDING = 23;
}
message EspNowUnicastTest {
@@ -67,11 +71,20 @@ message EspNowSlavePresence {
uint32 network = 1;
bytes mac = 2;
uint32 version = 3;
+ /** Preferred id from NVS for this master (0 = none / unbound). */
uint32 slave_id = 4;
bool available = 5;
bool used = 6;
}
+/** Master → slave: definitive logical client id for this session/master. */
+message EspNowAssignId {
+ uint32 assigned_id = 1;
+}
+
+/** Master → slave: clear persisted {master_mac, assigned_id} binding. */
+message EspNowClearIdBinding {}
+
message EspNowAccelDeadzone {
uint32 deadzone = 1;
uint32 client_id = 2; // 0 = all slaves; otherwise only matching slave_id applies
@@ -193,5 +206,7 @@ message EspNowMessage {
EspNowEchoPing echo_ping = 20;
EspNowEchoPong echo_pong = 21;
EspNowTapConfig tap_config = 22;
+ EspNowAssignId assign_id = 23;
+ EspNowClearIdBinding clear_id_binding = 24;
}
}
diff --git a/main/proto/uart_messages.pb.c b/main/proto/uart_messages.pb.c
index 245104f..7c2936b 100644
--- a/main/proto/uart_messages.pb.c
+++ b/main/proto/uart_messages.pb.c
@@ -114,6 +114,12 @@ PB_BIND(alox_RestartRequest, alox_RestartRequest, AUTO)
PB_BIND(alox_RestartResponse, alox_RestartResponse, AUTO)
+PB_BIND(alox_ClientIdMapResetRequest, alox_ClientIdMapResetRequest, AUTO)
+
+
+PB_BIND(alox_ClientIdMapResetResponse, alox_ClientIdMapResetResponse, AUTO)
+
+
PB_BIND(alox_SetLogLevelRequest, alox_SetLogLevelRequest, AUTO)
diff --git a/main/proto/uart_messages.pb.h b/main/proto/uart_messages.pb.h
index 440d953..dd328ed 100644
--- a/main/proto/uart_messages.pb.h
+++ b/main/proto/uart_messages.pb.h
@@ -40,7 +40,11 @@ typedef enum _alox_MessageType {
/* * Host → master: get/set BMA456H multitap detection parameters. */
alox_MessageType_TAP_CONFIG = 32,
/* * Master → host (BLE only): periodic pushed cache snapshot with ESP timestamp. */
- alox_MessageType_BLE_STREAM_TICK = 33
+ alox_MessageType_BLE_STREAM_TICK = 33,
+ /* *
+ Host → device: clear master client-id NVS map + live registry (and notify
+ online slaves), or on a slave clear local master/id binding only. */
+ alox_MessageType_CLIENT_ID_MAP_RESET = 34
} alox_MessageType;
typedef enum _alox_TapKind {
@@ -323,6 +327,15 @@ typedef struct _alox_RestartResponse {
uint32_t client_id;
} alox_RestartResponse;
+/* * Host → master or slave: clear persisted client-id assignment state. */
+typedef struct _alox_ClientIdMapResetRequest {
+ char dummy_field;
+} alox_ClientIdMapResetRequest;
+
+typedef struct _alox_ClientIdMapResetResponse {
+ bool success;
+} alox_ClientIdMapResetResponse;
+
/* * Host → master: read/write global log level (esp_log_level_set("*", …)). */
typedef struct _alox_SetLogLevelRequest {
bool write;
@@ -456,6 +469,8 @@ typedef struct _alox_UartMessage {
alox_TapConfigRequest tap_config_request;
alox_TapConfigResponse tap_config_response;
alox_BleStreamTick ble_stream_tick;
+ alox_ClientIdMapResetRequest client_id_map_reset_request;
+ alox_ClientIdMapResetResponse client_id_map_reset_response;
} payload;
} alox_UartMessage;
@@ -466,8 +481,8 @@ extern "C" {
/* Helper constants for enums */
#define _alox_MessageType_MIN alox_MessageType_UNKNOWN
-#define _alox_MessageType_MAX alox_MessageType_BLE_STREAM_TICK
-#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_BLE_STREAM_TICK+1))
+#define _alox_MessageType_MAX alox_MessageType_CLIENT_ID_MAP_RESET
+#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_CLIENT_ID_MAP_RESET+1))
#define _alox_TapKind_MIN alox_TapKind_TAP_NONE
#define _alox_TapKind_MAX alox_TapKind_TAP_TRIPLE
@@ -521,6 +536,8 @@ extern "C" {
+
+
@@ -561,6 +578,8 @@ extern "C" {
#define alox_EspNowFindMeResponse_init_default {0, 0}
#define alox_RestartRequest_init_default {0}
#define alox_RestartResponse_init_default {0, 0}
+#define alox_ClientIdMapResetRequest_init_default {0}
+#define alox_ClientIdMapResetResponse_init_default {0}
#define alox_SetLogLevelRequest_init_default {0, 0}
#define alox_SetLogLevelResponse_init_default {0, 0}
#define alox_TapConfigRequest_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
@@ -608,6 +627,8 @@ extern "C" {
#define alox_EspNowFindMeResponse_init_zero {0, 0}
#define alox_RestartRequest_init_zero {0}
#define alox_RestartResponse_init_zero {0, 0}
+#define alox_ClientIdMapResetRequest_init_zero {0}
+#define alox_ClientIdMapResetResponse_init_zero {0}
#define alox_SetLogLevelRequest_init_zero {0, 0}
#define alox_SetLogLevelResponse_init_zero {0, 0}
#define alox_TapConfigRequest_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
@@ -739,6 +760,7 @@ extern "C" {
#define alox_RestartRequest_client_id_tag 1
#define alox_RestartResponse_success_tag 1
#define alox_RestartResponse_client_id_tag 2
+#define alox_ClientIdMapResetResponse_success_tag 1
#define alox_SetLogLevelRequest_write_tag 1
#define alox_SetLogLevelRequest_level_tag 2
#define alox_SetLogLevelResponse_success_tag 1
@@ -822,6 +844,8 @@ extern "C" {
#define alox_UartMessage_tap_config_request_tag 39
#define alox_UartMessage_tap_config_response_tag 40
#define alox_UartMessage_ble_stream_tick_tag 41
+#define alox_UartMessage_client_id_map_reset_request_tag 42
+#define alox_UartMessage_client_id_map_reset_response_tag 43
/* Struct field encoding specification for nanopb */
#define alox_UartMessage_FIELDLIST(X, a) \
@@ -861,7 +885,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,set_log_level_request,payload.set_lo
X(a, STATIC, ONEOF, MESSAGE, (payload,set_log_level_response,payload.set_log_level_response), 38) \
X(a, STATIC, ONEOF, MESSAGE, (payload,tap_config_request,payload.tap_config_request), 39) \
X(a, STATIC, ONEOF, MESSAGE, (payload,tap_config_response,payload.tap_config_response), 40) \
-X(a, STATIC, ONEOF, MESSAGE, (payload,ble_stream_tick,payload.ble_stream_tick), 41)
+X(a, STATIC, ONEOF, MESSAGE, (payload,ble_stream_tick,payload.ble_stream_tick), 41) \
+X(a, STATIC, ONEOF, MESSAGE, (payload,client_id_map_reset_request,payload.client_id_map_reset_request), 42) \
+X(a, STATIC, ONEOF, MESSAGE, (payload,client_id_map_reset_response,payload.client_id_map_reset_response), 43)
#define alox_UartMessage_CALLBACK NULL
#define alox_UartMessage_DEFAULT NULL
#define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack
@@ -900,6 +926,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,ble_stream_tick,payload.ble_stream_t
#define alox_UartMessage_payload_tap_config_request_MSGTYPE alox_TapConfigRequest
#define alox_UartMessage_payload_tap_config_response_MSGTYPE alox_TapConfigResponse
#define alox_UartMessage_payload_ble_stream_tick_MSGTYPE alox_BleStreamTick
+#define alox_UartMessage_payload_client_id_map_reset_request_MSGTYPE alox_ClientIdMapResetRequest
+#define alox_UartMessage_payload_client_id_map_reset_response_MSGTYPE alox_ClientIdMapResetResponse
#define alox_Ack_FIELDLIST(X, a) \
@@ -1170,6 +1198,16 @@ X(a, STATIC, SINGULAR, UINT32, client_id, 2)
#define alox_RestartResponse_CALLBACK NULL
#define alox_RestartResponse_DEFAULT NULL
+#define alox_ClientIdMapResetRequest_FIELDLIST(X, a) \
+
+#define alox_ClientIdMapResetRequest_CALLBACK NULL
+#define alox_ClientIdMapResetRequest_DEFAULT NULL
+
+#define alox_ClientIdMapResetResponse_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, BOOL, success, 1)
+#define alox_ClientIdMapResetResponse_CALLBACK NULL
+#define alox_ClientIdMapResetResponse_DEFAULT NULL
+
#define alox_SetLogLevelRequest_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, write, 1) \
X(a, STATIC, SINGULAR, UINT32, level, 2)
@@ -1298,6 +1336,8 @@ extern const pb_msgdesc_t alox_EspNowFindMeRequest_msg;
extern const pb_msgdesc_t alox_EspNowFindMeResponse_msg;
extern const pb_msgdesc_t alox_RestartRequest_msg;
extern const pb_msgdesc_t alox_RestartResponse_msg;
+extern const pb_msgdesc_t alox_ClientIdMapResetRequest_msg;
+extern const pb_msgdesc_t alox_ClientIdMapResetResponse_msg;
extern const pb_msgdesc_t alox_SetLogLevelRequest_msg;
extern const pb_msgdesc_t alox_SetLogLevelResponse_msg;
extern const pb_msgdesc_t alox_TapConfigRequest_msg;
@@ -1347,6 +1387,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_EspNowFindMeResponse_fields &alox_EspNowFindMeResponse_msg
#define alox_RestartRequest_fields &alox_RestartRequest_msg
#define alox_RestartResponse_fields &alox_RestartResponse_msg
+#define alox_ClientIdMapResetRequest_fields &alox_ClientIdMapResetRequest_msg
+#define alox_ClientIdMapResetResponse_fields &alox_ClientIdMapResetResponse_msg
#define alox_SetLogLevelRequest_fields &alox_SetLogLevelRequest_msg
#define alox_SetLogLevelResponse_fields &alox_SetLogLevelResponse_msg
#define alox_TapConfigRequest_fields &alox_TapConfigRequest_msg
@@ -1382,6 +1424,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_CacheClientTap_size 8
#define alox_CacheStatusRequest_size 0
#define alox_CacheStatusResponse_size 736
+#define alox_ClientIdMapResetRequest_size 0
+#define alox_ClientIdMapResetResponse_size 2
#define alox_ClientInput_size 22
#define alox_EspNowEchoPingRequest_size 17
#define alox_EspNowEchoPingResponse_size 25
diff --git a/main/proto/uart_messages.proto b/main/proto/uart_messages.proto
index ef112d4..4d80abd 100644
--- a/main/proto/uart_messages.proto
+++ b/main/proto/uart_messages.proto
@@ -37,6 +37,11 @@ enum MessageType {
TAP_CONFIG = 32;
/** Master → host (BLE only): periodic pushed cache snapshot with ESP timestamp. */
BLE_STREAM_TICK = 33;
+ /**
+ * Host → device: clear master client-id NVS map + live registry (and notify
+ * online slaves), or on a slave clear local master/id binding only.
+ */
+ CLIENT_ID_MAP_RESET = 34;
}
message UartMessage {
@@ -78,6 +83,8 @@ message UartMessage {
TapConfigRequest tap_config_request = 39;
TapConfigResponse tap_config_response = 40;
BleStreamTick ble_stream_tick = 41;
+ ClientIdMapResetRequest client_id_map_reset_request = 42;
+ ClientIdMapResetResponse client_id_map_reset_response = 43;
}
}
@@ -349,6 +356,13 @@ message RestartResponse {
uint32 client_id = 2;
}
+/** Host → master or slave: clear persisted client-id assignment state. */
+message ClientIdMapResetRequest {}
+
+message ClientIdMapResetResponse {
+ bool success = 1;
+}
+
/** Host → master: read/write global log level (esp_log_level_set("*", …)). */
message SetLogLevelRequest {
bool write = 1;