Add UART SET_LOG_LEVEL for runtime master ESP-IDF logging.

Expose the command via goTool CLI/REST and dashboard controls so log verbosity can be tuned without reflashing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
simon 2026-06-06 18:03:34 +02:00
parent ac223ada72
commit 490e0ee61f
20 changed files with 655 additions and 69 deletions

View File

@ -207,7 +207,7 @@ sequenceDiagram
**Beispiel-Implementierung:** `main/cmd/cmd_accel_deadzone.c`
**Weitere Bridge-Handler:** `cmd_accel_stream.c`, `cmd_tap_notify.c`, `cmd_led_ring.c`, `cmd_espnow_find_me.c`, `cmd_restart.c`, `cmd_espnow_unicast_test.c`, `cmd_espnow_echo_ping.c`, `cmd/cmd_ota.c` (OTA + `ota_espnow.c`).
**Nur Master / nur Cache (kein Slave-Roundtrip):** `cmd_client_info.c`, `cmd_battery.c`, `cmd_cache_status.c`, `cmd_version.c`.
**Nur Master / nur Cache (kein Slave-Roundtrip):** `cmd_client_info.c`, `cmd_battery.c`, `cmd_cache_status.c`, `cmd_version.c`, `cmd_set_log_level.c`.
---

View File

@ -54,7 +54,9 @@ Zielbild für den Host: Befehle an den Master senden; der Master steuert Slaves
| LiPo ADC 1 | 1 | `board_input.c` |
| LiPo ADC 2 | 12 | Entfällt wenn = Taster-GPIO |
**UART:** `UART_NUM_1`, **921600** Baud, 8N1, kein Flow-Control.
**UART (Host-Protokoll):** `UART_NUM_1`, **921600** Baud, 8N1, kein Flow-Control.
**ESP-IDF-Log (Debug):** `esp_log_*` geht auf **UART0** (Standard-Konsole, typisch USB am Dev-Board, **115200** Baud, `CONFIG_ESP_CONSOLE_UART_NUM=0`). Das ist **getrennt** vom Host-UART1 — goTool liest keine `esp_log`-Ausgabe. Ohne angeschlossenes Debug-Kabel werden aktivierte Logs trotzdem formatiert und an UART0 gesendet (CPU-Overhead bleibt); nur `ESP_LOG_NONE` / Level-Filter vermeiden die Arbeit.
**I2C:** 100 kHz, interne Pull-ups, gemeinsamer Bus für Expander und BMA456H.
@ -233,6 +235,7 @@ Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uar
| 27 | TAP_NOTIFY | `cmd_tap_notify.c` | Tap-Weiterleitung konfigurieren |
| 29 | CACHE_STATUS | `cmd_cache_status.c` | Accel + Tap Cache (ein Round-Trip) |
| 30 | ESPNOW_ECHO_PING | `cmd_espnow_echo_ping.c` | Timestamp-Echo über ESP-NOW (Latenztest) |
| 31 | SET_LOG_LEVEL | `cmd_set_log_level.c` | ESP-IDF-Log-Level global (`"*"`) lesen/setzen |
### 7.1 VERSION (3)
@ -316,6 +319,28 @@ Round-Trip-Latenztest zu einem **Slave** (`client_id` > 0, muss in der Registry
**Host-seitig (goTool):** `rtt_ms` = volle UART-Kette (Send bis Response-Empfang); unabhängig von `esp_rtt_us`.
### 7.12 SET_LOG_LEVEL (31)
Laufzeit-Steuerung des **globalen** ESP-IDF-Log-Levels auf dem Master (`esp_log_level_set("*", …)`). Kein ESP-NOW, kein NVS — nur Master.
**Request:** `write`, `level` (`esp_log_level_t`: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE).
| `write` | Verhalten |
|---------|-----------|
| `false` / leer | Aktuelles Level lesen (`esp_log_level_get("*")`) |
| `true` | Level setzen; ungültige Werte (>5) → `success=false` |
**Response:** `success`, `level` (aktuell bzw. gesetzt).
**Boot-Default:** `CONFIG_LOG_DEFAULT_LEVEL` in `sdkconfig` (aktuell **INFO** = 3). Kann per UART/Web-UI zur Laufzeit geändert werden; nach Reboot gilt wieder der sdkconfig-Wert.
**Ausgabe:** Logs erscheinen auf **UART0** (Debug-USB), nicht auf dem Host-UART1 (goTool).
```bash
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3
```
---
## 8. OTA
@ -457,6 +482,7 @@ Includes in generierten `.pb.c` müssen `"uart_messages.pb.h"` heißen (nicht `m
| `cmd_restart.c` | RESTART |
| `cmd_espnow_unicast_test.c` | ESPNOW_UNICAST_TEST |
| `cmd_espnow_echo_ping.c` | ESPNOW_ECHO_PING |
| `cmd_set_log_level.c` | SET_LOG_LEVEL |
| `cmd_ota.c` | OTA_* |
| `cmd_ota_slave_progress.c` | OTA_SLAVE_PROGRESS |
@ -485,9 +511,12 @@ Includes in generierten `.pb.c` müssen `"uart_messages.pb.h"` heißen (nicht `m
## 15. Logging-Tags
**Level zur Laufzeit:** UART `SET_LOG_LEVEL` (31) oder Dashboard „ESP Log-Level“ — steuert nur die Konsolen-Ausgabe auf UART0, nicht das Host-Protokoll.
| Tag | Modul |
|-----|--------|
| `[Main]` | powerpod.c |
| `[LOG_LVL]` | cmd_set_log_level.c |
| `[UART]` | uart.c |
| `[CMDH]` | cmd_handler.c |
| `[UART_CMD]` | uart_cmd.c |

View File

@ -345,7 +345,7 @@ idf.py build
Ähnliche Features zum Abgucken:
- **Nur Master, kein ESP-NOW:** `main/cmd/cmd_version.c`, `main/cmd/cmd_led_ring.c`
- **Nur Master, kein ESP-NOW:** `main/cmd/cmd_version.c`, `main/cmd/cmd_led_ring.c`, `main/cmd/cmd_set_log_level.c`
- **Nur Slave per ESP-NOW (Master leitet nur durch):** `main/cmd/cmd_espnow_unicast_test.c`
- **Master + alle Slaves / Filter:** `main/cmd/cmd_accel_deadzone.c`
- **Großer ESP-NOW-Fluss mit Status:** `ota_espnow.c`, `main/cmd/cmd_ota.c`

View File

@ -36,6 +36,7 @@ go run . -port /dev/ttyUSB0 clients
| `led-ring` | 8 | LED ring: `-mode clear\|color\|progress\|digit\|blink\|find-me`, `-client`, `-all` |
| `find-me` | 22 | Locate pod (`-client 0` master, `>0` slave via ESP-NOW) |
| `restart` | 23 | Reboot master or slave (`-client 0` / `>0`) |
| `log-level` | `0x1f` | Get/set master ESP-IDF log level for tag `"*"` (`-set`, `-level` 05); output on UART0 debug, not host UART |
`clients` requires slaves to have responded to master discover broadcasts first.
@ -116,8 +117,12 @@ On success the slave serial log should show `UNICAST TEST OK from master … seq
```bash
go run . -port /dev/ttyUSB0 echo-ping -client 16
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3
```
`log-level` controls `esp_log_*` on the master (UART0 USB console). The host protocol UART (GPIO 2/3) is unchanged.
Measures latency to one slave. `rtt_ms` is the full host round-trip (UART + ESP-NOW + UART back). `esp_rtt_us` is the master-side ESP-NOW leg only (`esp_timer_get_time()` delta, raw microseconds from firmware).
Example output:

View File

@ -74,6 +74,17 @@ type restartAPIResponse struct {
Error string `json:"error,omitempty"`
}
type logLevelAPIResponse struct {
Success bool `json:"success"`
Level uint32 `json:"level"`
Error string `json:"error,omitempty"`
}
type logLevelAPIRequest struct {
Write bool `json:"write"`
Level uint32 `json:"level"`
}
type otaAPIResponse struct {
Success bool `json:"success"`
BytesWritten uint32 `json:"bytes_written,omitempty"`
@ -125,6 +136,16 @@ func mountServeAPI(mux *http.ServeMux, link *managedSerial, hub *wsHub, streamCt
}
serveRestart(w, r, link)
})
mux.HandleFunc("/api/log-level", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
serveLogLevelGet(w, r, link)
case http.MethodPost:
serveLogLevelPost(w, r, link)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
})
mux.HandleFunc("/api/ota", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@ -280,6 +301,43 @@ func applyDeadzoneToSlaves(link *managedSerial, deadzone uint32) (uint32, error)
return updated, nil
}
func serveLogLevelGet(w http.ResponseWriter, r *http.Request, link *managedSerial) {
resp, err := link.SetLogLevel(false, 0)
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, logLevelAPIResponse{Error: err.Error()})
return
}
writeJSON(w, http.StatusOK, logLevelAPIResponse{
Success: resp.GetSuccess(),
Level: resp.GetLevel(),
})
}
func serveLogLevelPost(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body logLevelAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, logLevelAPIResponse{Error: "invalid JSON"})
return
}
if !body.Write {
writeJSON(w, http.StatusBadRequest, logLevelAPIResponse{Error: "write must be true"})
return
}
if body.Level > 5 {
writeJSON(w, http.StatusBadRequest, logLevelAPIResponse{Error: "level must be 05"})
return
}
resp, err := link.SetLogLevel(true, body.Level)
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, logLevelAPIResponse{Error: err.Error()})
return
}
writeJSON(w, http.StatusOK, logLevelAPIResponse{
Success: resp.GetSuccess(),
Level: resp.GetLevel(),
})
}
func serveRestart(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body restartAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {

View File

@ -477,6 +477,16 @@ func (m *managedSerial) FindMe(clientID uint32) error {
})
}
func (m *managedSerial) SetLogLevel(write bool, level uint32) (*pb.SetLogLevelResponse, error) {
var resp *pb.SetLogLevelResponse
err := m.withPort(func(sp *serialPort) error {
var e error
resp, e = runSetLogLevelClient(sp, write, level)
return e
})
return resp, err
}
func (m *managedSerial) Restart(clientID uint32) error {
err := m.withPort(func(sp *serialPort) error {
return runRestartClient(sp, clientID)

61
goTool/cmd_log_level.go Normal file
View File

@ -0,0 +1,61 @@
package main
import (
"flag"
"fmt"
"google.golang.org/protobuf/proto"
"powerpod/gotool/pb"
)
func runLogLevel(sp *serialPort, args []string) error {
fs := flag.NewFlagSet("log-level", flag.ExitOnError)
write := fs.Bool("set", false, "write log level (default: read)")
level := fs.Uint("level", 0, "esp_log_level_t 05 (with -set)")
if err := fs.Parse(args); err != nil {
return err
}
resp, err := sp.setLogLevel(*write, uint32(*level))
if err != nil {
return err
}
if !resp.GetSuccess() {
return fmt.Errorf("set_log_level rejected (level=%d)", resp.GetLevel())
}
fmt.Printf("log_level=%d success=%v\n", resp.GetLevel(), resp.GetSuccess())
return nil
}
func runSetLogLevelClient(sp *serialPort, write bool, level uint32) (*pb.SetLogLevelResponse, error) {
return sp.setLogLevel(write, level)
}
func (s *serialPort) setLogLevel(write bool, level uint32) (*pb.SetLogLevelResponse, error) {
msg := &pb.UartMessage{
Type: pb.MessageType_SET_LOG_LEVEL,
Payload: &pb.UartMessage_SetLogLevelRequest{
SetLogLevelRequest: &pb.SetLogLevelRequest{
Write: write,
Level: level,
},
},
}
body, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
payload := append([]byte{byte(pb.MessageType_SET_LOG_LEVEL)}, body...)
respPayload, err := s.exchangePayload(payload, "SET_LOG_LEVEL")
if err != nil {
return nil, err
}
var respMsg pb.UartMessage
if err := proto.Unmarshal(respPayload[1:], &respMsg); err != nil {
return nil, fmt.Errorf("decode: %w", err)
}
r := respMsg.GetSetLogLevelResponse()
if r == nil {
return nil, fmt.Errorf("missing set_log_level_response")
}
return r, nil
}

View File

@ -282,6 +282,39 @@ Content-Type: application/json
{"client_id": 16}
```
### Log level (master ESP-IDF console)
Runtime get/set of the **global** log filter on the master (`esp_log_level_set("*", …)`). Output goes to **UART0** (USB debug, 115200), not the host protocol UART (GPIO 2/3, 921600).
```http
GET /api/log-level
POST /api/log-level
Content-Type: application/json
{"write": true, "level": 3}
```
| `level` | Meaning |
|---------|---------|
| 0 | None (no log output) |
| 1 | Error |
| 2 | Warn |
| 3 | Info |
| 4 | Debug |
| 5 | Verbose |
```json
{"success": true, "level": 3}
```
**CLI:**
```bash
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3
```
Dashboard: Master card → dropdown **ESP Log-Level** with **Lesen** / **Setzen**.
### OTA (master UART upload)
```http
@ -329,5 +362,6 @@ Same as external API:
| Alle Slaves deadzone | `all_clients` + `slaves_only` on POST |
| Unicast test | `POST /api/unicast-test` |
| Echo ping | `POST /api/echo-ping` (per-slave latency; UI shows Host ms + ESP ms) |
| Master log level | `GET` / `POST /api/log-level` or CLI `log-level` |
| Tap notify S/D/T | `PUT /api/clients/{id}/tap-notify` |
| Tap receive (UI) | Live stream + tap notify; see WebSocket doc for external API |

View File

@ -26,7 +26,8 @@ func usage() {
fmt.Fprintf(os.Stderr, " ota-progress query per-slave ESP-NOW OTA progress on master\n")
fmt.Fprintf(os.Stderr, " led-ring set LED ring progress bar (0100%%, rgb, intensity)\n")
fmt.Fprintf(os.Stderr, " find-me blink LED ring red/green/blue (3× each, full brightness)\n")
fmt.Fprintf(os.Stderr, " restart reboot master or slave (ESP-NOW)\n\n")
fmt.Fprintf(os.Stderr, " restart reboot master or slave (ESP-NOW)\n")
fmt.Fprintf(os.Stderr, " log-level get/set master ESP-IDF log level (global)\n\n")
flag.PrintDefaults()
}
@ -53,7 +54,7 @@ func main() {
os.Exit(2)
}
runErr = runServe(*portName, *baud, flag.Args()[1:])
case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-notify", "tap_notify", "cache-status", "cache_status", "unicast-test", "unicast_test", "echo-ping", "echo_ping", "led-ring", "led_ring", "find-me", "find_me", "restart", "ota", "ota-progress", "ota_progress":
case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-notify", "tap_notify", "cache-status", "cache_status", "unicast-test", "unicast_test", "echo-ping", "echo_ping", "led-ring", "led_ring", "find-me", "find_me", "restart", "log-level", "log_level", "ota", "ota-progress", "ota_progress":
if *portName == "" {
fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd)
usage()
@ -87,6 +88,8 @@ func main() {
runErr = runFindMe(sp, flag.Args()[1:])
case "restart":
runErr = runRestart(sp, flag.Args()[1:])
case "log-level", "log_level":
runErr = runLogLevel(sp, flag.Args()[1:])
case "ota":
runErr = runOTA(sp, flag.Args()[1:])
case "ota-progress", "ota_progress":

View File

@ -48,6 +48,8 @@ const (
MessageType_CACHE_STATUS MessageType = 29
// * Host → master → slave → master: timestamp echo round-trip (latency test).
MessageType_ESPNOW_ECHO_PING MessageType = 30
// * Host → master: get/set ESP-IDF log level for tag "*" (global).
MessageType_SET_LOG_LEVEL MessageType = 31
)
// Enum value maps for MessageType.
@ -75,6 +77,7 @@ var (
27: "TAP_NOTIFY",
29: "CACHE_STATUS",
30: "ESPNOW_ECHO_PING",
31: "SET_LOG_LEVEL",
}
MessageType_value = map[string]int32{
"UNKNOWN": 0,
@ -99,6 +102,7 @@ var (
"TAP_NOTIFY": 27,
"CACHE_STATUS": 29,
"ESPNOW_ECHO_PING": 30,
"SET_LOG_LEVEL": 31,
}
)
@ -217,6 +221,8 @@ type UartMessage struct {
// *UartMessage_CacheStatusResponse
// *UartMessage_EspnowEchoPingRequest
// *UartMessage_EspnowEchoPingResponse
// *UartMessage_SetLogLevelRequest
// *UartMessage_SetLogLevelResponse
Payload isUartMessage_Payload `protobuf_oneof:"payload"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
@ -545,6 +551,24 @@ func (x *UartMessage) GetEspnowEchoPingResponse() *EspNowEchoPingResponse {
return nil
}
func (x *UartMessage) GetSetLogLevelRequest() *SetLogLevelRequest {
if x != nil {
if x, ok := x.Payload.(*UartMessage_SetLogLevelRequest); ok {
return x.SetLogLevelRequest
}
}
return nil
}
func (x *UartMessage) GetSetLogLevelResponse() *SetLogLevelResponse {
if x != nil {
if x, ok := x.Payload.(*UartMessage_SetLogLevelResponse); ok {
return x.SetLogLevelResponse
}
}
return nil
}
type isUartMessage_Payload interface {
isUartMessage_Payload()
}
@ -673,6 +697,14 @@ type UartMessage_EspnowEchoPingResponse struct {
EspnowEchoPingResponse *EspNowEchoPingResponse `protobuf:"bytes,36,opt,name=espnow_echo_ping_response,json=espnowEchoPingResponse,proto3,oneof"`
}
type UartMessage_SetLogLevelRequest struct {
SetLogLevelRequest *SetLogLevelRequest `protobuf:"bytes,37,opt,name=set_log_level_request,json=setLogLevelRequest,proto3,oneof"`
}
type UartMessage_SetLogLevelResponse struct {
SetLogLevelResponse *SetLogLevelResponse `protobuf:"bytes,38,opt,name=set_log_level_response,json=setLogLevelResponse,proto3,oneof"`
}
func (*UartMessage_AckPayload) isUartMessage_Payload() {}
func (*UartMessage_EchoPayload) isUartMessage_Payload() {}
@ -735,6 +767,10 @@ func (*UartMessage_EspnowEchoPingRequest) isUartMessage_Payload() {}
func (*UartMessage_EspnowEchoPingResponse) isUartMessage_Payload() {}
func (*UartMessage_SetLogLevelRequest) isUartMessage_Payload() {}
func (*UartMessage_SetLogLevelResponse) isUartMessage_Payload() {}
type Ack struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
@ -2909,6 +2945,112 @@ func (x *RestartResponse) GetClientId() uint32 {
return 0
}
// * Host → master: read/write global log level (esp_log_level_set("*", …)).
type SetLogLevelRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Write bool `protobuf:"varint,1,opt,name=write,proto3" json:"write,omitempty"`
// * esp_log_level_t: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE
Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetLogLevelRequest) Reset() {
*x = SetLogLevelRequest{}
mi := &file_uart_messages_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetLogLevelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetLogLevelRequest) ProtoMessage() {}
func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[35]
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 SetLogLevelRequest.ProtoReflect.Descriptor instead.
func (*SetLogLevelRequest) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{35}
}
func (x *SetLogLevelRequest) GetWrite() bool {
if x != nil {
return x.Write
}
return false
}
func (x *SetLogLevelRequest) GetLevel() uint32 {
if x != nil {
return x.Level
}
return 0
}
type SetLogLevelResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SetLogLevelResponse) Reset() {
*x = SetLogLevelResponse{}
mi := &file_uart_messages_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SetLogLevelResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetLogLevelResponse) ProtoMessage() {}
func (x *SetLogLevelResponse) 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 SetLogLevelResponse.ProtoReflect.Descriptor instead.
func (*SetLogLevelResponse) Descriptor() ([]byte, []int) {
return file_uart_messages_proto_rawDescGZIP(), []int{36}
}
func (x *SetLogLevelResponse) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
func (x *SetLogLevelResponse) GetLevel() uint32 {
if x != nil {
return x.Level
}
return 0
}
// Host → device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS).
type OtaStartPayload struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -2919,7 +3061,7 @@ type OtaStartPayload struct {
func (x *OtaStartPayload) Reset() {
*x = OtaStartPayload{}
mi := &file_uart_messages_proto_msgTypes[35]
mi := &file_uart_messages_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2931,7 +3073,7 @@ func (x *OtaStartPayload) String() string {
func (*OtaStartPayload) ProtoMessage() {}
func (x *OtaStartPayload) ProtoReflect() protoreflect.Message {
mi := &file_uart_messages_proto_msgTypes[35]
mi := &file_uart_messages_proto_msgTypes[37]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2944,7 +3086,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{35}
return file_uart_messages_proto_rawDescGZIP(), []int{37}
}
func (x *OtaStartPayload) GetTotalSize() uint32 {
@ -2965,7 +3107,7 @@ type OtaPayload struct {
func (x *OtaPayload) Reset() {
*x = OtaPayload{}
mi := &file_uart_messages_proto_msgTypes[36]
mi := &file_uart_messages_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2977,7 +3119,7 @@ func (x *OtaPayload) String() string {
func (*OtaPayload) ProtoMessage() {}
func (x *OtaPayload) 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 {
@ -2990,7 +3132,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{36}
return file_uart_messages_proto_rawDescGZIP(), []int{38}
}
func (x *OtaPayload) GetSeq() uint32 {
@ -3016,7 +3158,7 @@ type OtaEndPayload struct {
func (x *OtaEndPayload) Reset() {
*x = OtaEndPayload{}
mi := &file_uart_messages_proto_msgTypes[37]
mi := &file_uart_messages_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -3028,7 +3170,7 @@ func (x *OtaEndPayload) String() string {
func (*OtaEndPayload) ProtoMessage() {}
func (x *OtaEndPayload) 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 {
@ -3041,7 +3183,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{37}
return file_uart_messages_proto_rawDescGZIP(), []int{39}
}
// Device → host status (also used as ACK after each 4 KiB written).
@ -3058,7 +3200,7 @@ type OtaStatusPayload struct {
func (x *OtaStatusPayload) Reset() {
*x = OtaStatusPayload{}
mi := &file_uart_messages_proto_msgTypes[38]
mi := &file_uart_messages_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -3070,7 +3212,7 @@ func (x *OtaStatusPayload) String() string {
func (*OtaStatusPayload) ProtoMessage() {}
func (x *OtaStatusPayload) 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 {
@ -3083,7 +3225,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{38}
return file_uart_messages_proto_rawDescGZIP(), []int{40}
}
func (x *OtaStatusPayload) GetStatus() uint32 {
@ -3124,7 +3266,7 @@ type OtaSlaveProgressRequest struct {
func (x *OtaSlaveProgressRequest) Reset() {
*x = OtaSlaveProgressRequest{}
mi := &file_uart_messages_proto_msgTypes[39]
mi := &file_uart_messages_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -3136,7 +3278,7 @@ func (x *OtaSlaveProgressRequest) String() string {
func (*OtaSlaveProgressRequest) ProtoMessage() {}
func (x *OtaSlaveProgressRequest) 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 {
@ -3149,7 +3291,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{39}
return file_uart_messages_proto_rawDescGZIP(), []int{41}
}
func (x *OtaSlaveProgressRequest) GetClientId() uint32 {
@ -3173,7 +3315,7 @@ type OtaSlaveProgressEntry struct {
func (x *OtaSlaveProgressEntry) Reset() {
*x = OtaSlaveProgressEntry{}
mi := &file_uart_messages_proto_msgTypes[40]
mi := &file_uart_messages_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -3185,7 +3327,7 @@ func (x *OtaSlaveProgressEntry) String() string {
func (*OtaSlaveProgressEntry) ProtoMessage() {}
func (x *OtaSlaveProgressEntry) 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 {
@ -3198,7 +3340,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{40}
return file_uart_messages_proto_rawDescGZIP(), []int{42}
}
func (x *OtaSlaveProgressEntry) GetClientId() uint32 {
@ -3249,7 +3391,7 @@ type OtaSlaveProgressResponse struct {
func (x *OtaSlaveProgressResponse) Reset() {
*x = OtaSlaveProgressResponse{}
mi := &file_uart_messages_proto_msgTypes[41]
mi := &file_uart_messages_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -3261,7 +3403,7 @@ func (x *OtaSlaveProgressResponse) String() string {
func (*OtaSlaveProgressResponse) ProtoMessage() {}
func (x *OtaSlaveProgressResponse) 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 {
@ -3274,7 +3416,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{41}
return file_uart_messages_proto_rawDescGZIP(), []int{43}
}
func (x *OtaSlaveProgressResponse) GetActive() bool {
@ -3316,7 +3458,7 @@ var File_uart_messages_proto protoreflect.FileDescriptor
const file_uart_messages_proto_rawDesc = "" +
"\n" +
"\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\x9f\x13\n" +
"\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\xc0\x14\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" +
@ -3353,7 +3495,9 @@ const file_uart_messages_proto_rawDesc = "" +
"\x14cache_status_request\x18! \x01(\v2\x18.alox.CacheStatusRequestH\x00R\x12cacheStatusRequest\x12O\n" +
"\x15cache_status_response\x18\" \x01(\v2\x19.alox.CacheStatusResponseH\x00R\x13cacheStatusResponse\x12V\n" +
"\x18espnow_echo_ping_request\x18# \x01(\v2\x1b.alox.EspNowEchoPingRequestH\x00R\x15espnowEchoPingRequest\x12Y\n" +
"\x19espnow_echo_ping_response\x18$ \x01(\v2\x1c.alox.EspNowEchoPingResponseH\x00R\x16espnowEchoPingResponseB\t\n" +
"\x19espnow_echo_ping_response\x18$ \x01(\v2\x1c.alox.EspNowEchoPingResponseH\x00R\x16espnowEchoPingResponse\x12M\n" +
"\x15set_log_level_request\x18% \x01(\v2\x18.alox.SetLogLevelRequestH\x00R\x12setLogLevelRequest\x12P\n" +
"\x16set_log_level_response\x18& \x01(\v2\x19.alox.SetLogLevelResponseH\x00R\x13setLogLevelResponseB\t\n" +
"\apayload\"\x05\n" +
"\x03Ack\"!\n" +
"\vEchoPayload\x12\x12\n" +
@ -3516,7 +3660,13 @@ 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\"0\n" +
"\tclient_id\x18\x02 \x01(\rR\bclientId\"@\n" +
"\x12SetLogLevelRequest\x12\x14\n" +
"\x05write\x18\x01 \x01(\bR\x05write\x12\x14\n" +
"\x05level\x18\x02 \x01(\rR\x05level\"E\n" +
"\x13SetLogLevelResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x14\n" +
"\x05level\x18\x02 \x01(\rR\x05level\"0\n" +
"\x0fOtaStartPayload\x12\x1d\n" +
"\n" +
"total_size\x18\x01 \x01(\rR\ttotalSize\":\n" +
@ -3547,7 +3697,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*\x87\x03\n" +
"\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\x9a\x03\n" +
"\vMessageType\x12\v\n" +
"\aUNKNOWN\x10\x00\x12\a\n" +
"\x03ACK\x10\x01\x12\b\n" +
@ -3572,7 +3722,8 @@ const file_uart_messages_proto_rawDesc = "" +
"\n" +
"TAP_NOTIFY\x10\x1b\x12\x10\n" +
"\fCACHE_STATUS\x10\x1d\x12\x14\n" +
"\x10ESPNOW_ECHO_PING\x10\x1e\"\x04\b\x18\x10\x18\"\x04\b\x1c\x10\x1c*G\n" +
"\x10ESPNOW_ECHO_PING\x10\x1e\x12\x11\n" +
"\rSET_LOG_LEVEL\x10\x1f\"\x04\b\x18\x10\x18\"\x04\b\x1c\x10\x1c*G\n" +
"\aTapKind\x12\f\n" +
"\bTAP_NONE\x10\x00\x12\x0e\n" +
"\n" +
@ -3595,7 +3746,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, 42)
var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 44)
var file_uart_messages_proto_goTypes = []any{
(MessageType)(0), // 0: alox.MessageType
(TapKind)(0), // 1: alox.TapKind
@ -3634,13 +3785,15 @@ var file_uart_messages_proto_goTypes = []any{
(*EspNowFindMeResponse)(nil), // 34: alox.EspNowFindMeResponse
(*RestartRequest)(nil), // 35: alox.RestartRequest
(*RestartResponse)(nil), // 36: alox.RestartResponse
(*OtaStartPayload)(nil), // 37: alox.OtaStartPayload
(*OtaPayload)(nil), // 38: alox.OtaPayload
(*OtaEndPayload)(nil), // 39: alox.OtaEndPayload
(*OtaStatusPayload)(nil), // 40: alox.OtaStatusPayload
(*OtaSlaveProgressRequest)(nil), // 41: alox.OtaSlaveProgressRequest
(*OtaSlaveProgressEntry)(nil), // 42: alox.OtaSlaveProgressEntry
(*OtaSlaveProgressResponse)(nil), // 43: alox.OtaSlaveProgressResponse
(*SetLogLevelRequest)(nil), // 37: alox.SetLogLevelRequest
(*SetLogLevelResponse)(nil), // 38: alox.SetLogLevelResponse
(*OtaStartPayload)(nil), // 39: alox.OtaStartPayload
(*OtaPayload)(nil), // 40: alox.OtaPayload
(*OtaEndPayload)(nil), // 41: alox.OtaEndPayload
(*OtaStatusPayload)(nil), // 42: alox.OtaStatusPayload
(*OtaSlaveProgressRequest)(nil), // 43: alox.OtaSlaveProgressRequest
(*OtaSlaveProgressEntry)(nil), // 44: alox.OtaSlaveProgressEntry
(*OtaSlaveProgressResponse)(nil), // 45: alox.OtaSlaveProgressResponse
}
var file_uart_messages_proto_depIdxs = []int32{
0, // 0: alox.UartMessage.type:type_name -> alox.MessageType
@ -3649,16 +3802,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
37, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload
38, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload
39, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload
40, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload
39, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload
40, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload
41, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload
42, // 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
27, // 12: alox.UartMessage.espnow_unicast_test_request:type_name -> alox.EspNowUnicastTestRequest
28, // 13: alox.UartMessage.espnow_unicast_test_response:type_name -> alox.EspNowUnicastTestResponse
41, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest
43, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse
43, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest
45, // 15: alox.UartMessage.ota_slave_progress_response:type_name -> alox.OtaSlaveProgressResponse
31, // 16: alox.UartMessage.led_ring_progress_request:type_name -> alox.LedRingProgressRequest
32, // 17: alox.UartMessage.led_ring_progress_response:type_name -> alox.LedRingProgressResponse
33, // 18: alox.UartMessage.espnow_find_me_request:type_name -> alox.EspNowFindMeRequest
@ -3675,22 +3828,24 @@ var file_uart_messages_proto_depIdxs = []int32{
26, // 29: alox.UartMessage.cache_status_response:type_name -> alox.CacheStatusResponse
29, // 30: alox.UartMessage.espnow_echo_ping_request:type_name -> alox.EspNowEchoPingRequest
30, // 31: alox.UartMessage.espnow_echo_ping_response:type_name -> alox.EspNowEchoPingResponse
6, // 32: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo
8, // 33: alox.ClientInputResponse.clients:type_name -> alox.ClientInput
15, // 34: alox.BatterySample.lipo1:type_name -> alox.LipoReading
15, // 35: alox.BatterySample.lipo2:type_name -> alox.LipoReading
16, // 36: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample
1, // 37: alox.TapEvent.kind:type_name -> alox.TapKind
1, // 38: alox.CacheClientTap.kind:type_name -> alox.TapKind
23, // 39: alox.CacheClientStatus.accel:type_name -> alox.CacheClientAccel
24, // 40: alox.CacheClientStatus.tap:type_name -> alox.CacheClientTap
25, // 41: alox.CacheStatusResponse.clients:type_name -> alox.CacheClientStatus
42, // 42: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry
43, // [43:43] is the sub-list for method output_type
43, // [43:43] is the sub-list for method input_type
43, // [43:43] is the sub-list for extension type_name
43, // [43:43] is the sub-list for extension extendee
0, // [0:43] is the sub-list for field type_name
37, // 32: alox.UartMessage.set_log_level_request:type_name -> alox.SetLogLevelRequest
38, // 33: alox.UartMessage.set_log_level_response:type_name -> alox.SetLogLevelResponse
6, // 34: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo
8, // 35: alox.ClientInputResponse.clients:type_name -> alox.ClientInput
15, // 36: alox.BatterySample.lipo1:type_name -> alox.LipoReading
15, // 37: alox.BatterySample.lipo2:type_name -> alox.LipoReading
16, // 38: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample
1, // 39: alox.TapEvent.kind:type_name -> alox.TapKind
1, // 40: alox.CacheClientTap.kind:type_name -> alox.TapKind
23, // 41: alox.CacheClientStatus.accel:type_name -> alox.CacheClientAccel
24, // 42: alox.CacheClientStatus.tap:type_name -> alox.CacheClientTap
25, // 43: alox.CacheStatusResponse.clients:type_name -> alox.CacheClientStatus
44, // 44: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry
45, // [45:45] is the sub-list for method output_type
45, // [45:45] is the sub-list for method input_type
45, // [45:45] is the sub-list for extension type_name
45, // [45:45] is the sub-list for extension extendee
0, // [0:45] is the sub-list for field type_name
}
func init() { file_uart_messages_proto_init() }
@ -3730,6 +3885,8 @@ func file_uart_messages_proto_init() {
(*UartMessage_CacheStatusResponse)(nil),
(*UartMessage_EspnowEchoPingRequest)(nil),
(*UartMessage_EspnowEchoPingResponse)(nil),
(*UartMessage_SetLogLevelRequest)(nil),
(*UartMessage_SetLogLevelResponse)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -3737,7 +3894,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: 42,
NumMessages: 44,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -219,6 +219,8 @@
<dd class="col-7" x-text="state.master.running_partition || '—'"></dd>
<dt class="col-5 text-muted">Deadzone</dt>
<dd class="col-7" x-text="state.master.deadzone != null ? state.master.deadzone + ' LSB' : '—'"></dd>
<dt class="col-5 text-muted">Log-Level</dt>
<dd class="col-7" x-text="formatLogLevel(masterLogLevel)"></dd>
<dt class="col-5 text-muted">LiPo 1</dt>
<dd class="col-7" x-text="formatLipo(state.master?.lipo1)"></dd>
<dt class="col-5 text-muted">LiPo 2</dt>
@ -264,6 +266,31 @@
Restart
</button>
</div>
<label class="form-label text-muted small mb-1 mt-3" for="master-log-level">
ESP Log-Level (global)
</label>
<div class="d-flex flex-wrap gap-2 align-items-end">
<select id="master-log-level" class="form-select form-select-sm config-input"
x-model.number="masterLogLevel"
:disabled="busy || !state.uart_connected">
<option value="0">None (aus)</option>
<option value="1">Error</option>
<option value="2">Warn</option>
<option value="3">Info</option>
<option value="4">Debug</option>
<option value="5">Verbose</option>
</select>
<button type="button" class="btn btn-outline-secondary btn-sm"
@click="readMasterLogLevel()"
:disabled="busy || !state.uart_connected">
Lesen
</button>
<button type="button" class="btn btn-primary btn-sm"
@click="setMasterLogLevel()"
:disabled="busy || !state.uart_connected">
Setzen
</button>
</div>
<p class="text-muted small mt-2 mb-0" x-show="!state.uart_connected">
UART nicht verbunden — Eingabe gesperrt.
</p>
@ -627,6 +654,7 @@
ws: null,
wsConnected: false,
masterDz: 100,
masterLogLevel: 0,
allDz: 100,
allTapSingle: false,
allTapDouble: false,
@ -766,6 +794,11 @@
if (data.samples?.length) this.applyBatterySamples(data.samples);
} catch (_) {}
},
formatLogLevel(level) {
const labels = ['None', 'Error', 'Warn', 'Info', 'Debug', 'Verbose'];
if (level == null || level < 0 || level > 5) return '—';
return `${labels[level]} (${level})`;
},
formatMac(hex) {
if (!hex || hex.length !== 12) return hex || '';
return hex.match(/.{2}/g).join(':');
@ -1104,6 +1137,44 @@
async setMasterDeadzone() {
await this.setDeadzone(0, this.masterDz);
},
async readMasterLogLevel() {
this.busy = true;
try {
const r = await fetch('/api/log-level');
const data = await r.json();
if (!r.ok || !data.success) {
this.flash(data.error || 'Log-Level lesen fehlgeschlagen', false);
return;
}
this.masterLogLevel = data.level;
this.flash(`Master: Log-Level ${this.formatLogLevel(data.level)}`, true);
} catch (e) {
this.flash(String(e), false);
} finally {
this.busy = false;
}
},
async setMasterLogLevel() {
this.busy = true;
try {
const r = await fetch('/api/log-level', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ write: true, level: this.masterLogLevel })
});
const data = await r.json();
if (!r.ok || !data.success) {
this.flash(data.error || 'Log-Level setzen fehlgeschlagen', false);
return;
}
this.masterLogLevel = data.level;
this.flash(`Master: Log-Level ${this.formatLogLevel(data.level)} gesetzt`, true);
} catch (e) {
this.flash(String(e), false);
} finally {
this.busy = false;
}
},
patchLiveStream(enabled) {
let clients = this.state.clients || [];
if (!enabled) {

View File

@ -28,6 +28,7 @@ idf_component_register(
"pod_reboot.c"
"cmd/cmd_led_ring.c"
"cmd/cmd_battery.c"
"cmd/cmd_set_log_level.c"
"cmd/cmd_ota.c"
"cmd/cmd_ota_slave_progress.c"
"ota_uart.c"

View File

@ -228,6 +228,8 @@ Host and master speak nanopb-encoded `UartMessage` inside UART frames (byte 0 =
| 25 | `ACCEL_STREAM` | Implemented — enable/disable slave ESP-NOW accel stream to master |
| 27 | `TAP_NOTIFY` | Implemented (`cmd/cmd_tap_notify.c`) — get/set which tap kinds notify via ESP-NOW |
| 29 | `CACHE_STATUS` | Implemented (`cmd/cmd_cache_status.c`) — subscribed accel + tap cache (one UART round-trip) |
| 30 | `ESPNOW_ECHO_PING` | Implemented (`cmd/cmd_espnow_echo_ping.c`) — ESP-NOW timestamp echo (latency test) |
| 31 | `SET_LOG_LEVEL` | Implemented (`cmd/cmd_set_log_level.c`) — get/set global `esp_log_level_set("*", …)` on master |
Regenerate C code:
@ -390,6 +392,30 @@ go run . -port /dev/ttyUSB0 find-me
go run . -port /dev/ttyUSB0 find-me -client 16
```
### SET_LOG_LEVEL command
Read or set the **global** ESP-IDF log filter on the master (`esp_log_level_set("*", level)`). Does not affect the host UART protocol (UART1); `esp_log_*` output goes to the **debug console UART0** (USB, 115200).
**Request:** framed `31` (`0x1f`) + `set_log_level_request` (`write`, `level` 05).
**Response:** `set_log_level_response` (`success`, `level`).
| `level` | `esp_log_level_t` |
|---------|-------------------|
| 0 | NONE |
| 1 | ERROR |
| 2 | WARN |
| 3 | INFO |
| 4 | DEBUG |
| 5 | VERBOSE |
Boot default follows `CONFIG_LOG_DEFAULT_LEVEL` in `sdkconfig` (not persisted across reboot).
```bash
go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 0
```
### RESTART command
Reboot the master (`client_id=0`) or one slave via ESP-NOW (`client_id` = registry id). The device sends the UART response, then restarts after ~150 ms.
@ -541,6 +567,7 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
| `pod_settings.c/h` | NVS persistence (accel deadzone, …) |
| `led_ring.c/h` | LED ring (digit display, progress bar) |
| `cmd/cmd_led_ring.c` | UART `LED_RING` progress command |
| `cmd/cmd_set_log_level.c` | UART `SET_LOG_LEVEL` — runtime ESP-IDF log level |
| `proto/uart_messages.proto` | UART protocol schema |
| `proto/esp_now_messages.proto` | ESP-NOW protocol schema |
| `esp_now_proto.c/h` | Encode/decode `EspNowMessage` |

View File

@ -59,6 +59,8 @@ static const char *message_type_name(uint16_t id) {
return "CACHE_STATUS";
case alox_MessageType_ESPNOW_ECHO_PING:
return "ESPNOW_ECHO_PING";
case alox_MessageType_SET_LOG_LEVEL:
return "SET_LOG_LEVEL";
default:
return "UNKNOWN";
}

View File

@ -0,0 +1,51 @@
#include "cmd_set_log_level.h"
#include "esp_log.h"
#include "uart_cmd.h"
static const char *TAG = "[LOG_LVL]";
static bool valid_level(uint32_t level) { return level <= ESP_LOG_VERBOSE; }
static void reply(uint32_t level, bool success) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_SET_LOG_LEVEL,
alox_UartMessage_set_log_level_response_tag);
response.payload.set_log_level_response.success = success;
response.payload.set_log_level_response.level = level;
uart_cmd_send(&response, TAG);
}
static void handle_set_log_level(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
alox_SetLogLevelRequest req = alox_SetLogLevelRequest_init_zero;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "decode failed");
reply((uint32_t)esp_log_level_get("*"), false);
return;
}
const alox_SetLogLevelRequest *req_ptr = UART_CMD_REQ(
&uart_msg, alox_UartMessage_set_log_level_request_tag, set_log_level_request);
if (req_ptr != NULL) {
req = *req_ptr;
}
if (req.write) {
if (!valid_level(req.level)) {
ESP_LOGW(TAG, "invalid level %lu", (unsigned long)req.level);
reply((uint32_t)esp_log_level_get("*"), false);
return;
}
esp_log_level_set("*", (esp_log_level_t)req.level);
ESP_LOGI(TAG, "global log level set to %lu", (unsigned long)req.level);
reply(req.level, true);
return;
}
reply((uint32_t)esp_log_level_get("*"), true);
}
void cmd_set_log_level_register(void) {
uart_cmd_register(alox_MessageType_SET_LOG_LEVEL, handle_set_log_level);
}

View File

@ -0,0 +1,6 @@
#ifndef CMD_SET_LOG_LEVEL_H
#define CMD_SET_LOG_LEVEL_H
void cmd_set_log_level_register(void);
#endif

View File

@ -14,6 +14,7 @@
#include "cmd_ota_slave_progress.h"
#include "cmd_led_ring.h"
#include "cmd_battery.h"
#include "cmd_set_log_level.h"
#include "esp_now_comm.h"
#include "powerpod.h"
#include "driver/gpio.h"
@ -73,9 +74,6 @@ uint8_t reverse_high_nibble_lut(uint8_t n) {
}
void app_main(void) {
esp_log_level_set("*", ESP_LOG_NONE);
if (pod_settings_init() != ESP_OK) {
ESP_LOGW(TAG, "settings NVS init failed; using defaults");
}
@ -196,6 +194,7 @@ void app_main(void) {
cmd_battery_register();
cmd_ota_register();
cmd_ota_slave_progress_register();
cmd_set_log_level_register();
}
ESP_LOGI(TAG, "LED ring: UART LED_RING commands only (no local demo loop)");

View File

@ -111,6 +111,12 @@ PB_BIND(alox_RestartRequest, alox_RestartRequest, AUTO)
PB_BIND(alox_RestartResponse, alox_RestartResponse, AUTO)
PB_BIND(alox_SetLogLevelRequest, alox_SetLogLevelRequest, AUTO)
PB_BIND(alox_SetLogLevelResponse, alox_SetLogLevelResponse, AUTO)
PB_BIND(alox_OtaStartPayload, alox_OtaStartPayload, AUTO)

View File

@ -34,7 +34,9 @@ typedef enum _alox_MessageType {
/* * Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */
alox_MessageType_CACHE_STATUS = 29,
/* * Host → master → slave → master: timestamp echo round-trip (latency test). */
alox_MessageType_ESPNOW_ECHO_PING = 30
alox_MessageType_ESPNOW_ECHO_PING = 30,
/* * Host → master: get/set ESP-IDF log level for tag "*" (global). */
alox_MessageType_SET_LOG_LEVEL = 31
} alox_MessageType;
typedef enum _alox_TapKind {
@ -307,6 +309,18 @@ typedef struct _alox_RestartResponse {
uint32_t client_id;
} alox_RestartResponse;
/* * Host → master: read/write global log level (esp_log_level_set("*", …)). */
typedef struct _alox_SetLogLevelRequest {
bool write;
/* * esp_log_level_t: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE */
uint32_t level;
} alox_SetLogLevelRequest;
typedef struct _alox_SetLogLevelResponse {
bool success;
uint32_t level;
} alox_SetLogLevelResponse;
/* Host → device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS). */
typedef struct _alox_OtaStartPayload {
uint32_t total_size;
@ -391,6 +405,8 @@ typedef struct _alox_UartMessage {
alox_CacheStatusResponse cache_status_response;
alox_EspNowEchoPingRequest espnow_echo_ping_request;
alox_EspNowEchoPingResponse espnow_echo_ping_response;
alox_SetLogLevelRequest set_log_level_request;
alox_SetLogLevelResponse set_log_level_response;
} payload;
} alox_UartMessage;
@ -401,8 +417,8 @@ extern "C" {
/* Helper constants for enums */
#define _alox_MessageType_MIN alox_MessageType_UNKNOWN
#define _alox_MessageType_MAX alox_MessageType_ESPNOW_ECHO_PING
#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_ESPNOW_ECHO_PING+1))
#define _alox_MessageType_MAX alox_MessageType_SET_LOG_LEVEL
#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_SET_LOG_LEVEL+1))
#define _alox_TapKind_MIN alox_TapKind_TAP_NONE
#define _alox_TapKind_MAX alox_TapKind_TAP_TRIPLE
@ -451,6 +467,8 @@ extern "C" {
@ -490,6 +508,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_SetLogLevelRequest_init_default {0, 0}
#define alox_SetLogLevelResponse_init_default {0, 0}
#define alox_OtaStartPayload_init_default {0}
#define alox_OtaPayload_init_default {0, {0, {0}}}
#define alox_OtaEndPayload_init_default {0}
@ -532,6 +552,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_SetLogLevelRequest_init_zero {0, 0}
#define alox_SetLogLevelResponse_init_zero {0, 0}
#define alox_OtaStartPayload_init_zero {0}
#define alox_OtaPayload_init_zero {0, {0, {0}}}
#define alox_OtaEndPayload_init_zero {0}
@ -655,6 +677,10 @@ extern "C" {
#define alox_RestartRequest_client_id_tag 1
#define alox_RestartResponse_success_tag 1
#define alox_RestartResponse_client_id_tag 2
#define alox_SetLogLevelRequest_write_tag 1
#define alox_SetLogLevelRequest_level_tag 2
#define alox_SetLogLevelResponse_success_tag 1
#define alox_SetLogLevelResponse_level_tag 2
#define alox_OtaStartPayload_total_size_tag 1
#define alox_OtaPayload_seq_tag 1
#define alox_OtaPayload_data_tag 2
@ -705,6 +731,8 @@ extern "C" {
#define alox_UartMessage_cache_status_response_tag 34
#define alox_UartMessage_espnow_echo_ping_request_tag 35
#define alox_UartMessage_espnow_echo_ping_response_tag 36
#define alox_UartMessage_set_log_level_request_tag 37
#define alox_UartMessage_set_log_level_response_tag 38
/* Struct field encoding specification for nanopb */
#define alox_UartMessage_FIELDLIST(X, a) \
@ -739,7 +767,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_notify_response,payload.tap_noti
X(a, STATIC, ONEOF, MESSAGE, (payload,cache_status_request,payload.cache_status_request), 33) \
X(a, STATIC, ONEOF, MESSAGE, (payload,cache_status_response,payload.cache_status_response), 34) \
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_echo_ping_request,payload.espnow_echo_ping_request), 35) \
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_echo_ping_response,payload.espnow_echo_ping_response), 36)
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_echo_ping_response,payload.espnow_echo_ping_response), 36) \
X(a, STATIC, ONEOF, MESSAGE, (payload,set_log_level_request,payload.set_log_level_request), 37) \
X(a, STATIC, ONEOF, MESSAGE, (payload,set_log_level_response,payload.set_log_level_response), 38)
#define alox_UartMessage_CALLBACK NULL
#define alox_UartMessage_DEFAULT NULL
#define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack
@ -773,6 +803,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_echo_ping_response,payload.es
#define alox_UartMessage_payload_cache_status_response_MSGTYPE alox_CacheStatusResponse
#define alox_UartMessage_payload_espnow_echo_ping_request_MSGTYPE alox_EspNowEchoPingRequest
#define alox_UartMessage_payload_espnow_echo_ping_response_MSGTYPE alox_EspNowEchoPingResponse
#define alox_UartMessage_payload_set_log_level_request_MSGTYPE alox_SetLogLevelRequest
#define alox_UartMessage_payload_set_log_level_response_MSGTYPE alox_SetLogLevelResponse
#define alox_Ack_FIELDLIST(X, a) \
@ -1034,6 +1066,18 @@ X(a, STATIC, SINGULAR, UINT32, client_id, 2)
#define alox_RestartResponse_CALLBACK NULL
#define alox_RestartResponse_DEFAULT NULL
#define alox_SetLogLevelRequest_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, write, 1) \
X(a, STATIC, SINGULAR, UINT32, level, 2)
#define alox_SetLogLevelRequest_CALLBACK NULL
#define alox_SetLogLevelRequest_DEFAULT NULL
#define alox_SetLogLevelResponse_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, success, 1) \
X(a, STATIC, SINGULAR, UINT32, level, 2)
#define alox_SetLogLevelResponse_CALLBACK NULL
#define alox_SetLogLevelResponse_DEFAULT NULL
#define alox_OtaStartPayload_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, total_size, 1)
#define alox_OtaStartPayload_CALLBACK NULL
@ -1117,6 +1161,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_SetLogLevelRequest_msg;
extern const pb_msgdesc_t alox_SetLogLevelResponse_msg;
extern const pb_msgdesc_t alox_OtaStartPayload_msg;
extern const pb_msgdesc_t alox_OtaPayload_msg;
extern const pb_msgdesc_t alox_OtaEndPayload_msg;
@ -1161,6 +1207,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_SetLogLevelRequest_fields &alox_SetLogLevelRequest_msg
#define alox_SetLogLevelResponse_fields &alox_SetLogLevelResponse_msg
#define alox_OtaStartPayload_fields &alox_OtaStartPayload_msg
#define alox_OtaPayload_fields &alox_OtaPayload_msg
#define alox_OtaEndPayload_fields &alox_OtaEndPayload_msg
@ -1210,6 +1258,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_OtaStatusPayload_size 24
#define alox_RestartRequest_size 6
#define alox_RestartResponse_size 8
#define alox_SetLogLevelRequest_size 8
#define alox_SetLogLevelResponse_size 8
#define alox_TapEvent_size 16
#define alox_TapNotifyRequest_size 16
#define alox_TapNotifyResponse_size 20

View File

@ -31,6 +31,8 @@ enum MessageType {
CACHE_STATUS = 29;
/** Host → master → slave → master: timestamp echo round-trip (latency test). */
ESPNOW_ECHO_PING = 30;
/** Host → master: get/set ESP-IDF log level for tag "*" (global). */
SET_LOG_LEVEL = 31;
}
message UartMessage {
@ -67,6 +69,8 @@ message UartMessage {
CacheStatusResponse cache_status_response = 34;
EspNowEchoPingRequest espnow_echo_ping_request = 35;
EspNowEchoPingResponse espnow_echo_ping_response = 36;
SetLogLevelRequest set_log_level_request = 37;
SetLogLevelResponse set_log_level_response = 38;
}
}
@ -329,6 +333,18 @@ message RestartResponse {
uint32 client_id = 2;
}
/** Host → master: read/write global log level (esp_log_level_set("*", …)). */
message SetLogLevelRequest {
bool write = 1;
/** esp_log_level_t: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE */
uint32 level = 2;
}
message SetLogLevelResponse {
bool success = 1;
uint32 level = 2;
}
// Host device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS).
message OtaStartPayload {
uint32 total_size = 1;