Assign sequential pod IDs from the master with NVS-stable bindings.

Replaces MAC-byte self-IDs with master allocation (1–255), slave preferred-ID reconnect, debug reset via UART/UI, and rate-limited ASSIGN to avoid ESP-NOW NO_MEM storms.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-01 17:25:07 +01:00
co-authored by Cursor
parent af20635047
commit 567dab6d0a
26 changed files with 1261 additions and 163 deletions
+25
View File
@@ -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 {
+4 -7
View File
@@ -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
}
+34 -3
View File
@@ -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)
}
}
+1 -1
View File
@@ -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 {
+55
View File
@@ -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)
}
+10 -2
View File
@@ -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":
+210 -79
View File
@@ -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,
},
+1 -1
View File
@@ -12,7 +12,7 @@
{
"id": "pod-1",
"mac": "50:78:7d:18:01:10",
"client_id": 16,
"client_id": 1,
"console": "/dev/ttyACM1"
}
]
+26
View File
@@ -540,6 +540,12 @@
title="Master-Tap-Config an alle Slaves (ESP-NOW)">
Tap-Cfg alle
</button>
<button type="button" class="btn btn-outline-danger btn-sm"
@click="resetClientIdMap()"
:disabled="busy || !hostUp()"
title="NVS-ID-Map + Live-Registry leeren; Online-Slaves verlieren Binding">
ID-Map leeren
</button>
<span class="badge bg-secondary" x-text="(state.clients || []).length + ' registered'"></span>
</div>
</div>
@@ -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 {
+1
View File
@@ -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"
+144 -18
View File
@@ -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;
+23 -1
View File
@@ -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);
+35
View File
@@ -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);
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void cmd_client_id_map_reset_register(void);
+16
View File
@@ -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);
+117 -7
View File
@@ -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);
}
}
+132 -36
View File
@@ -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) {
+251
View File
@@ -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;
}
+32
View File
@@ -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
+2
View File
@@ -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();
+6
View File
@@ -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)
+50 -4
View File
@@ -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
+15
View File
@@ -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;
}
}
+6
View File
@@ -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)
+48 -4
View File
@@ -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
+14
View File
@@ -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;