diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f7c3f9a..a87fa5f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -79,8 +79,9 @@ sequenceDiagram | Feld | Quelle | Bedeutung | |------|--------|-----------| -| `master` | `DIP_MASTER` (GPIO 4): Low = Master | Steuert UART + Registry-Nutzung | -| `network` | IO-Expander, Bits 5–8 (nibble reversed) | 1–8 → WiFi/ESP-NOW-Kanal | +| `master` | `DIP_MASTER` (GPIO 4): Low = Master | Steuert Host-Transport + Registry | +| `network` | IO-Expander 8er-DIP-Bank, High-Nibble PCB-reversed; DIP1–7 one-hot | 1–7 → WiFi/ESP-NOW-Kanal | +| `host_ble` | Expander DIP8 (nach Reverse+Invert) | `false`=UART, `true`=BLE (Boot only) | | `running_partition` | `esp_ota_get_running_partition()` | Aktives OTA-Label (`ota_0` / `ota_1`) | **Dateien:** `main/app_config.h`, `main/powerpod.c`, `main/powerpod.h` (Pins). @@ -96,17 +97,21 @@ sequenceDiagram ├─────────────────────────────────────────────────────────────┤ │ Dispatch: cmd_handler.c (Queue + vCmdDispatcherTask) │ ├─────────────────────────────────────────────────────────────┤ -│ Transport UART: uart.c (Framing) │ ESP-NOW: esp_now_comm │ +│ Host transport: uart.c / ble_host.c (Framing via host_transport) │ +│ ESP-NOW: esp_now_comm │ ├─────────────────────────────────────────────────────────────┤ │ Protokoll: uart_messages.proto │ esp_now_messages.proto │ │ Codec: nanopb (proto/*.pb.c) │ esp_now_proto.c │ ├─────────────────────────────────────────────────────────────┤ │ Domäne: client_registry, bosch456, led_ring, ota_*, board │ ├─────────────────────────────────────────────────────────────┤ -│ ESP-IDF: UART, WiFi, ESP-NOW, I2C, ADC, OTA, NVS, FreeRTOS │ +│ ESP-IDF: UART, NimBLE, WiFi, ESP-NOW, I2C, ADC, OTA, NVS │ └─────────────────────────────────────────────────────────────┘ ``` +**Host UART vs BLE:** Nur einer aktiv (DIP8). Gleiches Framing (`0xAA`…`0xCC`) und dieselbe `cmd_queue`. BLE: NUS-ähnliche GATT-Char, Write=RX, Notify=TX. OTA und Dashboard (`gotool serve`) bleiben UART-only. Bei BLE-Host und aktivem Accel-Stream pusht `ble_stream` periodisch `BLE_STREAM_TICK` (8–16 ms) mit `esp_timestamp_us`. + + --- ## 4. Datenfluss: Commands (Befehle) diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 21798fa..ee8a32d 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -47,14 +47,15 @@ Zielbild für den Host: Befehle an den Master senden; der Master steuert Slaves |--------|------|----------------| | DIP Master/Slave | 4 | `powerpod.h` — Low = Master | | I2C SCL / SDA | 5 / 6 | IO-Expander `0x20`, BMA456 `0x18` | -| UART1 TX / RX | **2 / 3** | `uart.h` (Adapter: ESP-TX → Host-RX) | +| Expander DIP-Bank (8) | PCF/TCA @ `0x20` | DIP1–7 = Netzwerk 1–7 (one-hot); **DIP8 ON = BLE host**, OFF = UART. High-Nibble auf PCB vertauscht → `reverse_high_nibble_lut`. Wechsel nur nach Reboot. | +| UART1 TX / RX | **2 / 3** | `uart.h` (Adapter: ESP-TX → Host-RX); nur wenn DIP8 = UART | | LED-Ring (WS2812) | 7 | `led_ring.c` | | BMA456 Interrupt | 10 | `bosch456.c` | | Taster | 12 | `board_input.c` | | LiPo ADC 1 | 1 | `board_input.c` | | LiPo ADC 2 | 11 | `board_input.c` | -**UART (Host-Protokoll):** `UART_NUM_1`, **921600** Baud, 8N1, kein Flow-Control. +**Host-Protokoll (UART oder BLE):** Gleiches Framing (`0xAA`…`0xCC`) + nanopb. **UART:** `UART_NUM_1`, **921600** Baud, 8N1. **BLE:** Gerät `Powerpod`, NUS-UUIDs `6e400001`/`6e400002` (Write=RX, Notify=TX). goTool: `-port …` oder `-ble` / `-ble-name Powerpod`. OTA und `gotool serve` nur über UART. Bei BLE + Accel-Stream: periodische `BLE_STREAM_TICK`-Notifies (`period_ms` 8–16). **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. @@ -94,20 +95,20 @@ idf.py -p /dev/ttyUSB0 flash monitor 2. `board_input_init_adc_only()` — LiPo-ADC 3. Bei `POWERPOD_BATTERY_UV_ENABLE`: `battery_uv_evaluate_boot()` → ggf. Energiesparmodus (nur LED + ADC, **kein** weiterer Init) 4. DIP → `app_config.master` -5. I2C + IO-Expander → `app_config.network` (1–8) +5. I2C + IO-Expander → `app_config.network` (1–7) + `app_config.host_ble` (DIP8) 6. `init_bma456()` — optional, bei Fehler weiter ohne Sensor 7. `pod_settings_apply_accel_deadzone()` wenn Sensor da 8. OTA-Partition → `app_config.running_partition` 9. `board_input_start_lipo_monitor()` + `board_input_init_button()` 10. `esp_now_comm_init(&app_config)` — **normaler Pfad** (Master + Slave) 11. `led_ring_init()` -12. **Nur Master:** `cmd_queue` → `init_cmdHandler` → `init_uart` → alle `cmd_*_register()` +12. **Nur Master:** `cmd_queue` → `init_cmdHandler` → `init_uart` **oder** `init_ble_host` → alle `cmd_*_register()` ### 4.2 Master vs. Slave | Funktion | Master | Slave | |----------|--------|-------| -| UART Command-Handler | Ja | Nein | +| Host Command-Handler (UART oder BLE) | Ja | Nein | | ESP-NOW Discover senden | Ja (Broadcast) | Nein | | ESP-NOW auf Discover reagieren | Nein | Ja | | `client_registry` für Fremdknoten | Ja | Nein (nur eigener Join-State) | @@ -233,10 +234,11 @@ Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uar | 21 | OTA_SLAVE_PROGRESS | `cmd_ota_slave_progress.c` | Fortschritt pro Slave | | 22 | FIND_ME | `cmd_espnow_find_me.c` | LED Locate | | 23 | RESTART | `cmd_restart.c` | Master oder Slave reboot | -| 25 | ACCEL_STREAM | `cmd_accel_stream.c` | ESP-NOW Accel-Stream an/aus | +| 25 | ACCEL_STREAM | `cmd_accel_stream.c` | ESP-NOW Accel-Stream an/aus (+ `period_ms` für BLE-Push) | | 26 | BATTERY_STATUS | `cmd_battery.c` | Cache LiPo Master + Slaves | | 27 | TAP_NOTIFY | `cmd_tap_notify.c` | Tap-Weiterleitung konfigurieren | | 29 | CACHE_STATUS | `cmd_cache_status.c` | Accel + Tap Cache (ein Round-Trip) | +| 33 | BLE_STREAM_TICK | `ble_stream.c` | Master→Host Notify (nur BLE): Snapshot + `esp_timestamp_us` | | 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 | | 32 | TAP_CONFIG | `cmd_tap_config.c` | BMA456 Multitap-Parameter + NVS | @@ -258,6 +260,7 @@ Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uar ### 7.4 ACCEL_STREAM (25) +- Optional `period_ms` (8–16): BLE-Host-Push-Intervall für `BLE_STREAM_TICK` (UART ignoriert; Default 16). - Master setzt `client_registry_set_accel_stream` und sendet `ESPNOW_SET_ACCEL_STREAM`. - Slave-Task `slave_accel_stream_task` sendet `ESPNOW_ACCEL_SAMPLE` alle 16 ms. diff --git a/goTool/ble_link.go b/goTool/ble_link.go new file mode 100644 index 0000000..f3cca01 --- /dev/null +++ b/goTool/ble_link.go @@ -0,0 +1,201 @@ +package main + +import ( + "fmt" + "log" + "sync" + "time" + + "tinygo.org/x/bluetooth" + + pb "powerpod/gotool/pb" + uartframe "powerpod/gotool/uart" +) + +const ( + bleServiceUUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" + bleCharUUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" + bleDefaultName = "Powerpod" +) + +type bleLink struct { + device bluetooth.Device + char bluetooth.DeviceCharacteristic + mu sync.Mutex + parser *uartframe.Parser + respCh chan []byte + streamCh chan []byte + quiet bool +} + +func openBLE(name string, settle time.Duration) (*bleLink, error) { + if name == "" { + name = bleDefaultName + } + adapter := bluetooth.DefaultAdapter + if err := adapter.Enable(); err != nil { + return nil, fmt.Errorf("enable bluetooth adapter: %w (check BlueZ / permissions)", err) + } + + svcUUID, err := bluetooth.ParseUUID(bleServiceUUID) + if err != nil { + return nil, err + } + chrUUID, err := bluetooth.ParseUUID(bleCharUUID) + if err != nil { + return nil, err + } + + log.Printf("BLE scanning for %q…", name) + var foundAddr bluetooth.Address + var found bool + go func() { + time.Sleep(15 * time.Second) + _ = adapter.StopScan() + }() + err = adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) { + if result.LocalName() != name { + return + } + foundAddr = result.Address + found = true + _ = a.StopScan() + }) + if err != nil { + return nil, fmt.Errorf("scan: %w", err) + } + if !found { + return nil, fmt.Errorf("device %q not found (check DIP8=BLE and advertising)", name) + } + + log.Printf("BLE connecting…") + device, err := adapter.Connect(foundAddr, bluetooth.ConnectionParams{}) + if err != nil { + return nil, fmt.Errorf("connect: %w", err) + } + + services, err := device.DiscoverServices([]bluetooth.UUID{svcUUID}) + if err != nil || len(services) == 0 { + _ = device.Disconnect() + return nil, fmt.Errorf("discover service: %v", err) + } + chars, err := services[0].DiscoverCharacteristics([]bluetooth.UUID{chrUUID}) + if err != nil || len(chars) == 0 { + _ = device.Disconnect() + return nil, fmt.Errorf("discover characteristic: %v", err) + } + char := chars[0] + + if mtu, err := char.GetMTU(); err == nil { + log.Printf("BLE ATT MTU=%d", mtu) + if mtu < 203 { + log.Printf("warning: MTU %d < 203 — large frames/stream ticks may fail", mtu) + } + } + + link := &bleLink{ + device: device, + char: char, + parser: uartframe.NewParser(), + respCh: make(chan []byte, 8), + streamCh: make(chan []byte, 64), + } + + if err := char.EnableNotifications(func(buf []byte) { + link.onNotify(buf) + }); err != nil { + _ = device.Disconnect() + return nil, fmt.Errorf("enable notifications: %w (CCCD required before commands)", err) + } + + if settle <= 0 { + settle = time.Second + } + log.Printf("BLE connected; settling %v…", settle) + time.Sleep(settle) + + return link, nil +} + +func (b *bleLink) onNotify(buf []byte) { + for _, by := range buf { + payload, ok, err := b.parser.Feed(by) + if err != nil || !ok { + continue + } + if len(payload) == 0 { + continue + } + if payload[0] == byte(pb.MessageType_BLE_STREAM_TICK) { + select { + case b.streamCh <- payload: + default: + } + continue + } + select { + case b.respCh <- payload: + default: + log.Printf("BLE: response channel full, dropping frame type=0x%02x", payload[0]) + } + } +} + +func (b *bleLink) Close() error { + return b.device.Disconnect() +} + +func (b *bleLink) exchange(cmdID byte, cmdName string) ([]byte, error) { + return b.exchangePayload([]byte{cmdID}, cmdName) +} + +func (b *bleLink) exchangePayload(payload []byte, cmdName string) ([]byte, error) { + b.mu.Lock() + defer b.mu.Unlock() + + frame, err := uartframe.EncodeFrame(payload) + if err != nil { + return nil, fmt.Errorf("encode frame: %w", err) + } + if !b.quiet { + log.Printf("BLE sending %s (%d bytes)", cmdName, len(frame)) + } + + for { + select { + case <-b.respCh: + default: + goto sent + } + } +sent: + if _, err := b.char.WriteWithoutResponse(frame); err != nil { + return nil, fmt.Errorf("BLE write: %w (check DIP8=BLE)", err) + } + + timer := time.NewTimer(readTimeout) + defer timer.Stop() + select { + case resp := <-b.respCh: + if !b.quiet { + log.Printf("BLE response (%d bytes)", len(resp)) + } + return resp, nil + case <-timer.C: + return nil, fmt.Errorf("BLE read timeout for %s (check DIP8=BLE / -ble)", cmdName) + } +} + +func (b *bleLink) NextStreamTick(timeout time.Duration) ([]byte, error) { + if timeout <= 0 { + timeout = 5 * time.Second + } + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case p := <-b.streamCh: + return p, nil + case <-timer.C: + return nil, fmt.Errorf("BLE stream tick timeout") + } +} diff --git a/goTool/cmd_accel_stream.go b/goTool/cmd_accel_stream.go new file mode 100644 index 0000000..b24e5ef --- /dev/null +++ b/goTool/cmd_accel_stream.go @@ -0,0 +1,44 @@ +package main + +import ( + "flag" + "fmt" + + pb "powerpod/gotool/pb" +) + +func runAccelStreamCmd(h hostConn, args []string) error { + fs := flag.NewFlagSet("accel-stream", flag.ExitOnError) + clientID := fs.Uint("client", 0, "slave client id (0 with -all)") + all := fs.Bool("all", false, "all registered clients") + enable := fs.Bool("enable", true, "enable stream") + disable := fs.Bool("disable", false, "disable stream") + period := fs.Uint("period", 0, "BLE push period_ms 8–16 (0=leave/default)") + if err := fs.Parse(args); err != nil { + return err + } + on := *enable + if *disable { + on = false + } + if !*all && *clientID == 0 { + return fmt.Errorf("need -client ID or -all") + } + req := &pb.AccelStreamRequest{ + Write: true, + Enable: on, + PeriodMs: uint32(*period), + } + if *all { + req.AllClients = true + } else { + req.ClientId = uint32(*clientID) + } + r, err := hostAccelStream(h, req) + if err != nil { + return err + } + fmt.Printf("accel-stream: success=%v enabled=%v client_id=%d slaves_updated=%d\n", + r.GetSuccess(), r.GetEnabled(), r.GetClientId(), r.GetSlavesUpdated()) + return nil +} diff --git a/goTool/cmd_ble_rtt.go b/goTool/cmd_ble_rtt.go new file mode 100644 index 0000000..6f03f91 --- /dev/null +++ b/goTool/cmd_ble_rtt.go @@ -0,0 +1,77 @@ +package main + +import ( + "flag" + "fmt" + "math" + "sort" + "time" +) + +func runBleRtt(h hostConn, args []string) error { + fs := flag.NewFlagSet("ble-rtt", flag.ExitOnError) + count := fs.Int("count", 50, "number of measured VERSION round-trips") + settle := fs.Float64("settle", 0, "extra settle seconds before measurement (0 = already settled on connect)") + warmup := fs.Int("warmup", 5, "discarded warm-up round-trips") + if err := fs.Parse(args); err != nil { + return err + } + if *count < 1 { + return fmt.Errorf("--count must be >= 1") + } + if *warmup < 0 { + return fmt.Errorf("--warmup must be >= 0") + } + + if *settle > 0 { + fmt.Printf("settling %.1fs…\n", *settle) + time.Sleep(time.Duration(*settle * float64(time.Second))) + } + + total := *warmup + *count + samples := make([]float64, 0, *count) + for i := 0; i < total; i++ { + t0 := time.Now() + if _, err := hostGetVersion(h); err != nil { + return fmt.Errorf("VERSION round-trip %d: %w", i, err) + } + ms := float64(time.Since(t0).Microseconds()) / 1000.0 + if i >= *warmup { + samples = append(samples, ms) + } + } + + sort.Float64s(samples) + fmt.Printf("ble-rtt (VERSION): n=%d warmup=%d\n", len(samples), *warmup) + fmt.Printf(" min=%.3f median=%.3f mean=%.3f max=%.3f ms\n", + samples[0], percentile(samples, 0.5), mean(samples), samples[len(samples)-1]) + return nil +} + +func mean(v []float64) float64 { + if len(v) == 0 { + return math.NaN() + } + var s float64 + for _, x := range v { + s += x + } + return s / float64(len(v)) +} + +func percentile(sorted []float64, p float64) float64 { + if len(sorted) == 0 { + return math.NaN() + } + if len(sorted) == 1 { + return sorted[0] + } + idx := p * float64(len(sorted)-1) + lo := int(idx) + hi := lo + 1 + if hi >= len(sorted) { + return sorted[lo] + } + frac := idx - float64(lo) + return sorted[lo]*(1-frac) + sorted[hi]*frac +} diff --git a/goTool/cmd_ble_stream.go b/goTool/cmd_ble_stream.go new file mode 100644 index 0000000..f386bb7 --- /dev/null +++ b/goTool/cmd_ble_stream.go @@ -0,0 +1,128 @@ +package main + +import ( + "flag" + "fmt" + "math" + "sort" + "time" + + pb "powerpod/gotool/pb" + "google.golang.org/protobuf/proto" +) + +func runBleStream(h hostConn, args []string) error { + fs := flag.NewFlagSet("ble-stream", flag.ExitOnError) + clientID := fs.Uint("client", 0, "slave client id (0 = all_clients)") + periodMs := fs.Uint("period", 16, "BLE push period_ms (8–16)") + count := fs.Int("count", 50, "number of BleStreamTick samples") + tolMs := fs.Float64("tol", 2.0, "tolerance ms around period for window_ok") + duration := fs.Float64("timeout", 15.0, "max seconds waiting for ticks") + if err := fs.Parse(args); err != nil { + return err + } + if *periodMs < 8 || *periodMs > 16 { + return fmt.Errorf("--period must be 8..16") + } + if *count < 2 { + return fmt.Errorf("--count must be >= 2") + } + + sc, ok := h.(hostStreamConn) + if !ok { + return fmt.Errorf("ble-stream requires -ble (UART has no push ticks)") + } + + req := &pb.AccelStreamRequest{ + Write: true, + Enable: true, + PeriodMs: uint32(*periodMs), + } + if *clientID == 0 { + req.AllClients = true + } else { + req.ClientId = uint32(*clientID) + } + resp, err := hostAccelStream(h, req) + if err != nil { + return err + } + if !resp.GetSuccess() { + return fmt.Errorf("accel stream enable failed") + } + fmt.Printf("accel stream on; period_ms=%d; collecting %d ticks…\n", *periodMs, *count) + + type sample struct { + espUs uint64 + pcT float64 + } + samples := make([]sample, 0, *count) + deadline := time.Now().Add(time.Duration(*duration * float64(time.Second))) + + for len(samples) < *count { + remain := time.Until(deadline) + if remain <= 0 { + break + } + payload, err := sc.NextStreamTick(remain) + if err != nil { + break + } + pcT := time.Now().UnixMicro() + var msg pb.UartMessage + if err := proto.Unmarshal(payload[1:], &msg); err != nil { + continue + } + tick := msg.GetBleStreamTick() + if tick == nil { + continue + } + samples = append(samples, sample{espUs: tick.GetEspTimestampUs(), pcT: float64(pcT)}) + } + + // Best-effort disable + off := &pb.AccelStreamRequest{Write: true, Enable: false, AllClients: req.AllClients, ClientId: req.ClientId} + _, _ = hostAccelStream(h, off) + + if len(samples) < 2 { + return fmt.Errorf("need >=2 ticks, got %d (is stream running / DIP8=BLE?)", len(samples)) + } + + var dT, dR, extra []float64 + esp0 := samples[0].espUs + pc0 := samples[0].pcT + for i := 1; i < len(samples); i++ { + dT = append(dT, float64(samples[i].espUs-samples[i-1].espUs)/1000.0) + dR = append(dR, (samples[i].pcT-samples[i-1].pcT)/1000.0) + relEsp := float64(samples[i].espUs-esp0) / 1000.0 + relPc := (samples[i].pcT - pc0) / 1000.0 + extra = append(extra, relPc-relEsp) + } + + period := float64(*periodMs) + outCount := 0 + for _, dt := range dT { + if math.Abs(dt-period) > *tolMs { + outCount++ + } + } + windowOK := outCount*2 <= len(dT) // majority inside tolerance + + fmt.Printf("received=%d/%d period_ms=%d tol=±%.1f ms\n", len(samples), *count, *periodMs, *tolMs) + printStat("dT ESP inter-depart", dT) + printStat("dR PC inter-arrive", dR) + printStat("extra vs first", extra) + fmt.Printf("outside_tol=%d/%d window_ok=%s\n", outCount, len(dT), map[bool]string{true: "yes", false: "no"}[windowOK]) + return nil +} + +func printStat(name string, v []float64) { + if len(v) == 0 { + fmt.Printf("%s: (none)\n", name) + return + } + s := append([]float64(nil), v...) + sort.Float64s(s) + fmt.Printf("%s: n=%d min=%.3f median=%.3f mean=%.3f max=%.3f ms\n", + name, len(s), s[0], percentile(s, 0.5), mean(s), s[len(s)-1]) +} diff --git a/goTool/go.mod b/goTool/go.mod index 406f670..8d0aa8d 100644 --- a/goTool/go.mod +++ b/goTool/go.mod @@ -6,9 +6,19 @@ require ( github.com/gorilla/websocket v1.5.3 go.bug.st/serial v1.6.4 google.golang.org/protobuf v1.36.11 + tinygo.org/x/bluetooth v0.13.0 ) require ( github.com/creack/goselect v0.1.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/soypat/cyw43439 v0.0.0-20250505012923-830110c8f4af // indirect + github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 // indirect + github.com/tinygo-org/cbgo v0.0.4 // indirect + github.com/tinygo-org/pio v0.2.0 // indirect + golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect golang.org/x/sys v0.19.0 // indirect ) diff --git a/goTool/go.sum b/goTool/go.sum index d63f03f..6976541 100644 --- a/goTool/go.sum +++ b/goTool/go.sum @@ -1,20 +1,51 @@ github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b h1:du3zG5fd8snsFN6RBoLA7fpaYV9ZQIsyH9snlk2Zvik= +github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soypat/cyw43439 v0.0.0-20250505012923-830110c8f4af h1:ZfFq94aH/BCSWWKd9RPUgdHOdgGKCnfl2VdvU9UksTA= +github.com/soypat/cyw43439 v0.0.0-20250505012923-830110c8f4af/go.mod h1:MUaGO5m6X7xrkHrPDmnaxCEcuCCFN/0ZFh9oie+exbU= +github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 h1:Y9fBuiR/urFY/m76+SAZTxk2xAOS2n85f+H1CugajeA= +github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU= +github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk= +github.com/tinygo-org/pio v0.2.0 h1:vo3xa6xDZ2rVtxrks/KcTZHF3qq4lyWOntvEvl2pOhU= +github.com/tinygo-org/pio v0.2.0/go.mod h1:LU7Dw00NJ+N86QkeTGjMLNkYcEYMor6wTDpTCu0EaH8= go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +tinygo.org/x/bluetooth v0.13.0 h1:3pkTMcfqv71HoAxG4DBTm2n+1bm6Nqqz8eoHjSW9+5g= +tinygo.org/x/bluetooth v0.13.0/go.mod h1:YnyJRVX09i+wkFeHpXut0b+qHq+T2WwKBRRiF/scANA= diff --git a/goTool/host_conn.go b/goTool/host_conn.go new file mode 100644 index 0000000..e5e3b3b --- /dev/null +++ b/goTool/host_conn.go @@ -0,0 +1,125 @@ +package main + +import ( + "fmt" + "time" + + pb "powerpod/gotool/pb" + "google.golang.org/protobuf/proto" +) + +// hostConn is the UART or BLE host transport used by CLI commands. +type hostConn interface { + exchangePayload(payload []byte, cmdName string) ([]byte, error) + exchange(cmdID byte, cmdName string) ([]byte, error) + Close() error +} + +// optional stream source (BLE only) +type hostStreamConn interface { + hostConn + // NextStreamTick waits for a BLE_STREAM_TICK payload (type byte + protobuf). + NextStreamTick(timeout time.Duration) ([]byte, error) +} + +func hostGetVersion(h hostConn) (*pb.VersionResponse, error) { + payload, err := h.exchange(byte(pb.MessageType_VERSION), "VERSION") + if err != nil { + return nil, err + } + return decodeVersionPayload(payload) +} + +func hostListClients(h hostConn) ([]*pb.ClientInfo, error) { + payload, err := h.exchange(byte(pb.MessageType_CLIENT_INFO), "CLIENT_INFO") + if err != nil { + return nil, err + } + return decodeClientsPayload(payload) +} + +func hostReadCacheStatus(h hostConn) (*pb.CacheStatusResponse, error) { + payload, err := h.exchange(byte(pb.MessageType_CACHE_STATUS), "CACHE_STATUS") + if err != nil { + return nil, err + } + return decodeCacheStatusPayload(payload) +} + +func hostAccelStream(h hostConn, req *pb.AccelStreamRequest) (*pb.AccelStreamResponse, error) { + msg := &pb.UartMessage{ + Type: pb.MessageType_ACCEL_STREAM, + Payload: &pb.UartMessage_AccelStreamRequest{ + AccelStreamRequest: req, + }, + } + body, err := proto.Marshal(msg) + if err != nil { + return nil, fmt.Errorf("encode: %w", err) + } + payload := append([]byte{byte(pb.MessageType_ACCEL_STREAM)}, body...) + respPayload, err := h.exchangePayload(payload, "ACCEL_STREAM") + 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.GetAccelStreamResponse() + if r == nil { + return nil, fmt.Errorf("missing accel_stream_response") + } + return r, nil +} + +func hostEchoPing(h hostConn, clientID uint32) (*EchoPingResult, error) { + t0 := time.Now() + timestampUs := uint64(t0.UnixMicro()) + req := &pb.EspNowEchoPingRequest{ + ClientId: clientID, + TimestampUs: timestampUs, + } + msg := &pb.UartMessage{ + Type: pb.MessageType_ESPNOW_ECHO_PING, + Payload: &pb.UartMessage_EspnowEchoPingRequest{ + EspnowEchoPingRequest: req, + }, + } + body, err := proto.Marshal(msg) + if err != nil { + return nil, fmt.Errorf("encode: %w", err) + } + payload := append([]byte{byte(pb.MessageType_ESPNOW_ECHO_PING)}, body...) + respPayload, err := h.exchangePayload(payload, "ESPNOW_ECHO_PING") + if err != nil { + return nil, err + } + rttMs := float64(time.Since(t0).Microseconds()) / 1000.0 + + var respMsg pb.UartMessage + if err := proto.Unmarshal(respPayload[1:], &respMsg); err != nil { + return nil, fmt.Errorf("decode: %w", err) + } + r := respMsg.GetEspnowEchoPingResponse() + if r == nil { + return nil, fmt.Errorf("missing espnow_echo_ping_response") + } + if !r.GetSuccess() { + return &EchoPingResult{ + Success: false, + ClientID: r.GetClientId(), + RttMs: rttMs, + }, nil + } + if r.GetTimestampUs() != timestampUs { + return nil, fmt.Errorf("timestamp mismatch: sent %d got %d", timestampUs, r.GetTimestampUs()) + } + return &EchoPingResult{ + Success: true, + ClientID: r.GetClientId(), + TimestampUs: r.GetTimestampUs(), + RttMs: rttMs, + EspRttUs: r.GetEspRttUs(), + }, nil +} diff --git a/goTool/main.go b/goTool/main.go index 8613863..51ff699 100644 --- a/goTool/main.go +++ b/goTool/main.go @@ -5,12 +5,14 @@ import ( "fmt" "log" "os" + "time" ) const defaultBaud = 921600 func usage() { - fmt.Fprintf(os.Stderr, "usage: gotool [-port /dev/ttyUSB0] \n") + fmt.Fprintf(os.Stderr, "usage: gotool [-port /dev/ttyUSB0 | -ble] \n") + fmt.Fprintf(os.Stderr, " DIP8 on master: 0=UART (-port), 1=BLE (-ble); reboot after DIP change\n") 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") @@ -18,12 +20,15 @@ func usage() { 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") - fmt.Fprintf(os.Stderr, " cache-status subscribed accel + tap cache (one UART round-trip)\n") + fmt.Fprintf(os.Stderr, " cache-status subscribed accel + tap cache (one round-trip)\n") + fmt.Fprintf(os.Stderr, " accel-stream enable/disable ESP-NOW accel stream (+ BLE period)\n") fmt.Fprintf(os.Stderr, " unicast-test send ESP-NOW unicast test to one slave\n") fmt.Fprintf(os.Stderr, " echo-ping ESP-NOW timestamp echo round-trip to one slave\n") + fmt.Fprintf(os.Stderr, " ble-rtt Host↔Master RTT via VERSION (N samples)\n") + fmt.Fprintf(os.Stderr, " ble-stream enable stream + measure BleStreamTick window\n") fmt.Fprintf(os.Stderr, " test run automated scenario (see testdata/)\n") - fmt.Fprintf(os.Stderr, " serve web dashboard (Bootstrap + WebSocket)\n") - fmt.Fprintf(os.Stderr, " ota UART OTA upload (A/B partitions)\n") + fmt.Fprintf(os.Stderr, " serve web dashboard (UART only)\n") + fmt.Fprintf(os.Stderr, " ota UART OTA upload (A/B partitions; UART host only)\n") 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 (0–100%%, rgb, intensity)\n") fmt.Fprintf(os.Stderr, " find-me blink LED ring red/green/blue (3× each, full brightness)\n") @@ -33,8 +38,11 @@ func usage() { } func main() { - portName := flag.String("port", "", "serial port (e.g. /dev/ttyUSB0)") + portName := flag.String("port", "", "serial port (e.g. /dev/ttyUSB0); XOR with -ble") baud := flag.Int("baud", defaultBaud, "UART baud rate") + useBLE := flag.Bool("ble", false, "use BLE host transport (DIP8=1); XOR with -port") + bleName := flag.String("ble-name", bleDefaultName, "BLE advertised name") + bleSettle := flag.Float64("ble-settle", 1.0, "seconds to settle after BLE connect") flag.Parse() if flag.NArg() < 1 { @@ -47,55 +55,133 @@ func main() { var runErr error switch cmd { case "test", "autotest": + if *useBLE { + runErr = fmt.Errorf("autotest requires -port (UART)") + break + } runErr = runTest(*portName, *baud, flag.Args()[1:]) case "serve", "web", "dashboard": + if *useBLE { + fmt.Fprintf(os.Stderr, "serve requires -port (UART only in this release)\n") + os.Exit(2) + } if *portName == "" { fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd) usage() os.Exit(2) } runErr = runServe(*portName, *baud, flag.Args()[1:]) - case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-config", "tap_config", "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) + 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": + if *useBLE && *portName != "" { + fmt.Fprintf(os.Stderr, "use either -port or -ble, not both\n") + os.Exit(2) + } + if !*useBLE && *portName == "" { + fmt.Fprintf(os.Stderr, "command %q requires -port or -ble\n\n", cmd) usage() os.Exit(2) } - sp, err := openSerial(*portName, *baud) - if err != nil { - log.Fatalf("open serial: %v", err) + if cmd == "ota" || cmd == "ota-progress" || cmd == "ota_progress" { + if *useBLE { + fmt.Fprintf(os.Stderr, "OTA requires UART host (DIP8=0, -port)\n") + os.Exit(2) + } } - registerShutdown(func() { _ = sp.Close() }) + + var h hostConn + if *useBLE { + bl, err := openBLE(*bleName, time.Duration(*bleSettle*float64(time.Second))) + if err != nil { + log.Fatalf("open BLE: %v", err) + } + h = bl + } else { + sp, err := openSerial(*portName, *baud) + if err != nil { + log.Fatalf("open serial: %v", err) + } + h = sp + } + registerShutdown(func() { _ = h.Close() }) enableShutdownOnInterrupt() - defer sp.Close() + defer h.Close() + switch cmd { case "version": - runErr = runVersion(sp) + runErr = runVersionHost(h) case "clients", "client-info": - runErr = runClients(sp) + runErr = runClientsHost(h) + case "cache-status", "cache_status": + runErr = runCacheStatusHost(h) + case "accel-stream", "accel_stream": + runErr = runAccelStreamCmd(h, flag.Args()[1:]) + case "echo-ping", "echo_ping": + runErr = runEchoPingHost(h, flag.Args()[1:]) + case "ble-rtt", "ble_rtt": + runErr = runBleRtt(h, flag.Args()[1:]) + case "ble-stream", "ble_stream": + runErr = runBleStream(h, flag.Args()[1:]) case "deadzone", "accel-deadzone": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runDeadzone(sp, flag.Args()[1:]) case "tap-config", "tap_config": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runTapConfig(sp, flag.Args()[1:]) case "tap-notify", "tap_notify": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runTapNotify(sp, flag.Args()[1:]) - case "cache-status", "cache_status": - runErr = runCacheStatus(sp) case "unicast-test", "unicast_test": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runUnicastTest(sp, flag.Args()[1:]) - case "echo-ping", "echo_ping": - runErr = runEchoPing(sp, flag.Args()[1:]) case "led-ring", "led_ring": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runLedRing(sp, flag.Args()[1:]) case "find-me", "find_me": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runFindMe(sp, flag.Args()[1:]) case "restart": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runRestart(sp, flag.Args()[1:]) case "log-level", "log_level": + sp, ok := h.(*serialPort) + if !ok { + runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd) + break + } runErr = runLogLevel(sp, flag.Args()[1:]) case "ota": + sp := h.(*serialPort) runErr = runOTA(sp, flag.Args()[1:]) case "ota-progress", "ota_progress": + sp := h.(*serialPort) runErr = runOtaProgress(sp, flag.Args()[1:]) } default: @@ -108,3 +194,76 @@ func main() { log.Fatal(runErr) } } + +func runVersionHost(h hostConn) error { + ver, err := hostGetVersion(h) + if err != nil { + return err + } + fmt.Printf("version: %d\n", ver.GetVersion()) + fmt.Printf("git_hash: %s\n", ver.GetGitHash()) + return nil +} + +func runClientsHost(h hostConn) error { + clients, err := hostListClients(h) + if err != nil { + return err + } + if len(clients) == 0 { + fmt.Println("no clients registered") + return nil + } + fmt.Printf("clients (%d):\n", len(clients)) + for i, c := range clients { + fmt.Printf(" [%d] id=%d ver=%d available=%v used=%v\n", + i, c.GetId(), c.GetVersion(), c.GetAvailable(), c.GetUsed()) + } + return nil +} + +func runCacheStatusHost(h hostConn) error { + r, err := hostReadCacheStatus(h) + if err != nil { + return err + } + clients := r.GetClients() + if len(clients) == 0 { + fmt.Println("(no slaves with accel stream or tap notify enabled)") + return nil + } + for _, c := range clients { + id := c.GetClientId() + if a := c.GetAccel(); a != nil { + if !a.GetValid() { + fmt.Printf("client %d accel: no sample yet\n", id) + } else { + fmt.Printf("client %d accel: x=%d y=%d z=%d (age %d ms)\n", + id, a.GetX(), a.GetY(), a.GetZ(), a.GetAgeMs()) + } + } + if t := c.GetTap(); t != nil { + fmt.Printf("client %d tap: %s (age %d ms)\n", + id, tapKindLabel(t.GetKind()), t.GetAgeMs()) + } + } + return nil +} + +func runEchoPingHost(h hostConn, args []string) error { + fs := flag.NewFlagSet("echo-ping", flag.ExitOnError) + clientID := fs.Uint("client", 0, "slave client id from `clients`") + if err := fs.Parse(args); err != nil { + return err + } + if *clientID == 0 { + return fmt.Errorf("client id required (see `gotool clients`)") + } + r, err := hostEchoPing(h, uint32(*clientID)) + if err != nil { + return err + } + fmt.Printf("echo ping: success=%v client_id=%d rtt_ms=%.3f (host↔master↔slave) esp_rtt_us=%d (ESP-NOW only)\n", + r.Success, r.ClientID, r.RttMs, r.EspRttUs) + return nil +} diff --git a/goTool/pb/uart_messages.pb.go b/goTool/pb/uart_messages.pb.go index 08cd7b4..742504e 100644 --- a/goTool/pb/uart_messages.pb.go +++ b/goTool/pb/uart_messages.pb.go @@ -52,6 +52,8 @@ const ( MessageType_SET_LOG_LEVEL MessageType = 31 // * Host → master: get/set BMA456H multitap detection parameters. MessageType_TAP_CONFIG MessageType = 32 + // * Master → host (BLE only): periodic pushed cache snapshot with ESP timestamp. + MessageType_BLE_STREAM_TICK MessageType = 33 ) // Enum value maps for MessageType. @@ -81,6 +83,7 @@ var ( 30: "ESPNOW_ECHO_PING", 31: "SET_LOG_LEVEL", 32: "TAP_CONFIG", + 33: "BLE_STREAM_TICK", } MessageType_value = map[string]int32{ "UNKNOWN": 0, @@ -107,6 +110,7 @@ var ( "ESPNOW_ECHO_PING": 30, "SET_LOG_LEVEL": 31, "TAP_CONFIG": 32, + "BLE_STREAM_TICK": 33, } ) @@ -229,6 +233,7 @@ type UartMessage struct { // *UartMessage_SetLogLevelResponse // *UartMessage_TapConfigRequest // *UartMessage_TapConfigResponse + // *UartMessage_BleStreamTick Payload isUartMessage_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -593,6 +598,15 @@ func (x *UartMessage) GetTapConfigResponse() *TapConfigResponse { return nil } +func (x *UartMessage) GetBleStreamTick() *BleStreamTick { + if x != nil { + if x, ok := x.Payload.(*UartMessage_BleStreamTick); ok { + return x.BleStreamTick + } + } + return nil +} + type isUartMessage_Payload interface { isUartMessage_Payload() } @@ -737,6 +751,10 @@ type UartMessage_TapConfigResponse struct { TapConfigResponse *TapConfigResponse `protobuf:"bytes,40,opt,name=tap_config_response,json=tapConfigResponse,proto3,oneof"` } +type UartMessage_BleStreamTick struct { + BleStreamTick *BleStreamTick `protobuf:"bytes,41,opt,name=ble_stream_tick,json=bleStreamTick,proto3,oneof"` +} + func (*UartMessage_AckPayload) isUartMessage_Payload() {} func (*UartMessage_EchoPayload) isUartMessage_Payload() {} @@ -807,6 +825,8 @@ func (*UartMessage_TapConfigRequest) isUartMessage_Payload() {} func (*UartMessage_TapConfigResponse) isUartMessage_Payload() {} +func (*UartMessage_BleStreamTick) isUartMessage_Payload() {} + type Ack struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -1371,12 +1391,14 @@ func (x *AccelDeadzoneResponse) GetSlavesUpdated() uint32 { // Host → master: enable/disable slave accel ESP-NOW stream (~16 ms per slave). // write=false: read; write=true: apply. client_id 0 invalid for write (use >0 or all_clients). +// period_ms (8–16): BLE host push interval for BleStreamTick; ignored on UART (default 16). type AccelStreamRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Write bool `protobuf:"varint,1,opt,name=write,proto3" json:"write,omitempty"` Enable bool `protobuf:"varint,2,opt,name=enable,proto3" json:"enable,omitempty"` ClientId uint32 `protobuf:"varint,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` AllClients bool `protobuf:"varint,4,opt,name=all_clients,json=allClients,proto3" json:"all_clients,omitempty"` + PeriodMs uint32 `protobuf:"varint,5,opt,name=period_ms,json=periodMs,proto3" json:"period_ms,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1439,6 +1461,13 @@ func (x *AccelStreamRequest) GetAllClients() bool { return false } +func (x *AccelStreamRequest) GetPeriodMs() uint32 { + if x != nil { + return x.PeriodMs + } + return 0 +} + type AccelStreamResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` @@ -2333,6 +2362,67 @@ func (x *CacheStatusResponse) GetClients() []*CacheClientStatus { return nil } +// * Master → host over BLE notify: aggregated cache + ESP timer timestamp. +type BleStreamTick struct { + state protoimpl.MessageState `protogen:"open.v1"` + EspTimestampUs uint64 `protobuf:"varint,1,opt,name=esp_timestamp_us,json=espTimestampUs,proto3" json:"esp_timestamp_us,omitempty"` + PeriodMs uint32 `protobuf:"varint,2,opt,name=period_ms,json=periodMs,proto3" json:"period_ms,omitempty"` + Clients []*CacheClientStatus `protobuf:"bytes,3,rep,name=clients,proto3" json:"clients,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BleStreamTick) Reset() { + *x = BleStreamTick{} + mi := &file_uart_messages_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BleStreamTick) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BleStreamTick) ProtoMessage() {} + +func (x *BleStreamTick) ProtoReflect() protoreflect.Message { + mi := &file_uart_messages_proto_msgTypes[25] + 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 BleStreamTick.ProtoReflect.Descriptor instead. +func (*BleStreamTick) Descriptor() ([]byte, []int) { + return file_uart_messages_proto_rawDescGZIP(), []int{25} +} + +func (x *BleStreamTick) GetEspTimestampUs() uint64 { + if x != nil { + return x.EspTimestampUs + } + return 0 +} + +func (x *BleStreamTick) GetPeriodMs() uint32 { + if x != nil { + return x.PeriodMs + } + return 0 +} + +func (x *BleStreamTick) GetClients() []*CacheClientStatus { + if x != nil { + return x.Clients + } + return nil +} + type EspNowUnicastTestRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ClientId uint32 `protobuf:"varint,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` @@ -2343,7 +2433,7 @@ type EspNowUnicastTestRequest struct { func (x *EspNowUnicastTestRequest) Reset() { *x = EspNowUnicastTestRequest{} - mi := &file_uart_messages_proto_msgTypes[25] + mi := &file_uart_messages_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2355,7 +2445,7 @@ func (x *EspNowUnicastTestRequest) String() string { func (*EspNowUnicastTestRequest) ProtoMessage() {} func (x *EspNowUnicastTestRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[25] + mi := &file_uart_messages_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2368,7 +2458,7 @@ func (x *EspNowUnicastTestRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowUnicastTestRequest.ProtoReflect.Descriptor instead. func (*EspNowUnicastTestRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{25} + return file_uart_messages_proto_rawDescGZIP(), []int{26} } func (x *EspNowUnicastTestRequest) GetClientId() uint32 { @@ -2395,7 +2485,7 @@ type EspNowUnicastTestResponse struct { func (x *EspNowUnicastTestResponse) Reset() { *x = EspNowUnicastTestResponse{} - mi := &file_uart_messages_proto_msgTypes[26] + mi := &file_uart_messages_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2407,7 +2497,7 @@ func (x *EspNowUnicastTestResponse) String() string { func (*EspNowUnicastTestResponse) ProtoMessage() {} func (x *EspNowUnicastTestResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[26] + mi := &file_uart_messages_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2420,7 +2510,7 @@ func (x *EspNowUnicastTestResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowUnicastTestResponse.ProtoReflect.Descriptor instead. func (*EspNowUnicastTestResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{26} + return file_uart_messages_proto_rawDescGZIP(), []int{27} } func (x *EspNowUnicastTestResponse) GetSuccess() bool { @@ -2449,7 +2539,7 @@ type EspNowEchoPingRequest struct { func (x *EspNowEchoPingRequest) Reset() { *x = EspNowEchoPingRequest{} - mi := &file_uart_messages_proto_msgTypes[27] + mi := &file_uart_messages_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2461,7 +2551,7 @@ func (x *EspNowEchoPingRequest) String() string { func (*EspNowEchoPingRequest) ProtoMessage() {} func (x *EspNowEchoPingRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[27] + mi := &file_uart_messages_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2474,7 +2564,7 @@ func (x *EspNowEchoPingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowEchoPingRequest.ProtoReflect.Descriptor instead. func (*EspNowEchoPingRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{27} + return file_uart_messages_proto_rawDescGZIP(), []int{28} } func (x *EspNowEchoPingRequest) GetClientId() uint32 { @@ -2505,7 +2595,7 @@ type EspNowEchoPingResponse struct { func (x *EspNowEchoPingResponse) Reset() { *x = EspNowEchoPingResponse{} - mi := &file_uart_messages_proto_msgTypes[28] + mi := &file_uart_messages_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2517,7 +2607,7 @@ func (x *EspNowEchoPingResponse) String() string { func (*EspNowEchoPingResponse) ProtoMessage() {} func (x *EspNowEchoPingResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[28] + mi := &file_uart_messages_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2530,7 +2620,7 @@ func (x *EspNowEchoPingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowEchoPingResponse.ProtoReflect.Descriptor instead. func (*EspNowEchoPingResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{28} + return file_uart_messages_proto_rawDescGZIP(), []int{29} } func (x *EspNowEchoPingResponse) GetSuccess() bool { @@ -2591,7 +2681,7 @@ type LedRingProgressRequest struct { func (x *LedRingProgressRequest) Reset() { *x = LedRingProgressRequest{} - mi := &file_uart_messages_proto_msgTypes[29] + mi := &file_uart_messages_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2603,7 +2693,7 @@ func (x *LedRingProgressRequest) String() string { func (*LedRingProgressRequest) ProtoMessage() {} func (x *LedRingProgressRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[29] + mi := &file_uart_messages_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2616,7 +2706,7 @@ func (x *LedRingProgressRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LedRingProgressRequest.ProtoReflect.Descriptor instead. func (*LedRingProgressRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{29} + return file_uart_messages_proto_rawDescGZIP(), []int{30} } func (x *LedRingProgressRequest) GetMode() uint32 { @@ -2717,7 +2807,7 @@ type LedRingProgressResponse struct { func (x *LedRingProgressResponse) Reset() { *x = LedRingProgressResponse{} - mi := &file_uart_messages_proto_msgTypes[30] + mi := &file_uart_messages_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2729,7 +2819,7 @@ func (x *LedRingProgressResponse) String() string { func (*LedRingProgressResponse) ProtoMessage() {} func (x *LedRingProgressResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[30] + mi := &file_uart_messages_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2742,7 +2832,7 @@ func (x *LedRingProgressResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LedRingProgressResponse.ProtoReflect.Descriptor instead. func (*LedRingProgressResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{30} + return file_uart_messages_proto_rawDescGZIP(), []int{31} } func (x *LedRingProgressResponse) GetSuccess() bool { @@ -2797,7 +2887,7 @@ type EspNowFindMeRequest struct { func (x *EspNowFindMeRequest) Reset() { *x = EspNowFindMeRequest{} - mi := &file_uart_messages_proto_msgTypes[31] + mi := &file_uart_messages_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2809,7 +2899,7 @@ func (x *EspNowFindMeRequest) String() string { func (*EspNowFindMeRequest) ProtoMessage() {} func (x *EspNowFindMeRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[31] + mi := &file_uart_messages_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2822,7 +2912,7 @@ func (x *EspNowFindMeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowFindMeRequest.ProtoReflect.Descriptor instead. func (*EspNowFindMeRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{31} + return file_uart_messages_proto_rawDescGZIP(), []int{32} } func (x *EspNowFindMeRequest) GetClientId() uint32 { @@ -2842,7 +2932,7 @@ type EspNowFindMeResponse struct { func (x *EspNowFindMeResponse) Reset() { *x = EspNowFindMeResponse{} - mi := &file_uart_messages_proto_msgTypes[32] + mi := &file_uart_messages_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2854,7 +2944,7 @@ func (x *EspNowFindMeResponse) String() string { func (*EspNowFindMeResponse) ProtoMessage() {} func (x *EspNowFindMeResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[32] + mi := &file_uart_messages_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2867,7 +2957,7 @@ func (x *EspNowFindMeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EspNowFindMeResponse.ProtoReflect.Descriptor instead. func (*EspNowFindMeResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{32} + return file_uart_messages_proto_rawDescGZIP(), []int{33} } func (x *EspNowFindMeResponse) GetSuccess() bool { @@ -2894,7 +2984,7 @@ type RestartRequest struct { func (x *RestartRequest) Reset() { *x = RestartRequest{} - mi := &file_uart_messages_proto_msgTypes[33] + mi := &file_uart_messages_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2906,7 +2996,7 @@ func (x *RestartRequest) String() string { func (*RestartRequest) ProtoMessage() {} func (x *RestartRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[33] + mi := &file_uart_messages_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2919,7 +3009,7 @@ func (x *RestartRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartRequest.ProtoReflect.Descriptor instead. func (*RestartRequest) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{33} + return file_uart_messages_proto_rawDescGZIP(), []int{34} } func (x *RestartRequest) GetClientId() uint32 { @@ -2939,7 +3029,7 @@ type RestartResponse struct { func (x *RestartResponse) Reset() { *x = RestartResponse{} - mi := &file_uart_messages_proto_msgTypes[34] + mi := &file_uart_messages_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2951,7 +3041,7 @@ func (x *RestartResponse) String() string { func (*RestartResponse) ProtoMessage() {} func (x *RestartResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[34] + mi := &file_uart_messages_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2964,7 +3054,7 @@ func (x *RestartResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartResponse.ProtoReflect.Descriptor instead. func (*RestartResponse) Descriptor() ([]byte, []int) { - return file_uart_messages_proto_rawDescGZIP(), []int{34} + return file_uart_messages_proto_rawDescGZIP(), []int{35} } func (x *RestartResponse) GetSuccess() bool { @@ -2993,7 +3083,7 @@ type SetLogLevelRequest struct { func (x *SetLogLevelRequest) Reset() { *x = SetLogLevelRequest{} - mi := &file_uart_messages_proto_msgTypes[35] + mi := &file_uart_messages_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3005,7 +3095,7 @@ func (x *SetLogLevelRequest) String() string { func (*SetLogLevelRequest) ProtoMessage() {} func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[35] + mi := &file_uart_messages_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3018,7 +3108,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{35} + return file_uart_messages_proto_rawDescGZIP(), []int{36} } func (x *SetLogLevelRequest) GetWrite() bool { @@ -3045,7 +3135,7 @@ type SetLogLevelResponse struct { func (x *SetLogLevelResponse) Reset() { *x = SetLogLevelResponse{} - mi := &file_uart_messages_proto_msgTypes[36] + mi := &file_uart_messages_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3057,7 +3147,7 @@ func (x *SetLogLevelResponse) String() string { func (*SetLogLevelResponse) ProtoMessage() {} func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[36] + mi := &file_uart_messages_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3070,7 +3160,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{36} + return file_uart_messages_proto_rawDescGZIP(), []int{37} } func (x *SetLogLevelResponse) GetSuccess() bool { @@ -3109,7 +3199,7 @@ type TapConfigRequest struct { func (x *TapConfigRequest) Reset() { *x = TapConfigRequest{} - mi := &file_uart_messages_proto_msgTypes[37] + mi := &file_uart_messages_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3121,7 +3211,7 @@ func (x *TapConfigRequest) String() string { func (*TapConfigRequest) ProtoMessage() {} func (x *TapConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[37] + mi := &file_uart_messages_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3134,7 +3224,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{37} + return file_uart_messages_proto_rawDescGZIP(), []int{38} } func (x *TapConfigRequest) GetWrite() bool { @@ -3241,7 +3331,7 @@ type TapConfigResponse struct { func (x *TapConfigResponse) Reset() { *x = TapConfigResponse{} - mi := &file_uart_messages_proto_msgTypes[38] + mi := &file_uart_messages_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3253,7 +3343,7 @@ func (x *TapConfigResponse) String() string { func (*TapConfigResponse) ProtoMessage() {} func (x *TapConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[38] + mi := &file_uart_messages_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3266,7 +3356,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{38} + return file_uart_messages_proto_rawDescGZIP(), []int{39} } func (x *TapConfigResponse) GetClientId() uint32 { @@ -3363,7 +3453,7 @@ type OtaStartPayload struct { func (x *OtaStartPayload) Reset() { *x = OtaStartPayload{} - mi := &file_uart_messages_proto_msgTypes[39] + mi := &file_uart_messages_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3375,7 +3465,7 @@ func (x *OtaStartPayload) String() string { func (*OtaStartPayload) ProtoMessage() {} func (x *OtaStartPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[39] + mi := &file_uart_messages_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3388,7 +3478,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{39} + return file_uart_messages_proto_rawDescGZIP(), []int{40} } func (x *OtaStartPayload) GetTotalSize() uint32 { @@ -3409,7 +3499,7 @@ type OtaPayload struct { func (x *OtaPayload) Reset() { *x = OtaPayload{} - mi := &file_uart_messages_proto_msgTypes[40] + mi := &file_uart_messages_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3421,7 +3511,7 @@ func (x *OtaPayload) String() string { func (*OtaPayload) ProtoMessage() {} func (x *OtaPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[40] + mi := &file_uart_messages_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3434,7 +3524,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{40} + return file_uart_messages_proto_rawDescGZIP(), []int{41} } func (x *OtaPayload) GetSeq() uint32 { @@ -3460,7 +3550,7 @@ type OtaEndPayload struct { func (x *OtaEndPayload) Reset() { *x = OtaEndPayload{} - mi := &file_uart_messages_proto_msgTypes[41] + mi := &file_uart_messages_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3472,7 +3562,7 @@ func (x *OtaEndPayload) String() string { func (*OtaEndPayload) ProtoMessage() {} func (x *OtaEndPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[41] + mi := &file_uart_messages_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3485,7 +3575,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{41} + return file_uart_messages_proto_rawDescGZIP(), []int{42} } // Device → host status (also used as ACK after each 4 KiB written). @@ -3502,7 +3592,7 @@ type OtaStatusPayload struct { func (x *OtaStatusPayload) Reset() { *x = OtaStatusPayload{} - mi := &file_uart_messages_proto_msgTypes[42] + mi := &file_uart_messages_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3514,7 +3604,7 @@ func (x *OtaStatusPayload) String() string { func (*OtaStatusPayload) ProtoMessage() {} func (x *OtaStatusPayload) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[42] + mi := &file_uart_messages_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3527,7 +3617,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{42} + return file_uart_messages_proto_rawDescGZIP(), []int{43} } func (x *OtaStatusPayload) GetStatus() uint32 { @@ -3568,7 +3658,7 @@ type OtaSlaveProgressRequest struct { func (x *OtaSlaveProgressRequest) Reset() { *x = OtaSlaveProgressRequest{} - mi := &file_uart_messages_proto_msgTypes[43] + mi := &file_uart_messages_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3580,7 +3670,7 @@ func (x *OtaSlaveProgressRequest) String() string { func (*OtaSlaveProgressRequest) ProtoMessage() {} func (x *OtaSlaveProgressRequest) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[43] + mi := &file_uart_messages_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3593,7 +3683,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{43} + return file_uart_messages_proto_rawDescGZIP(), []int{44} } func (x *OtaSlaveProgressRequest) GetClientId() uint32 { @@ -3617,7 +3707,7 @@ type OtaSlaveProgressEntry struct { func (x *OtaSlaveProgressEntry) Reset() { *x = OtaSlaveProgressEntry{} - mi := &file_uart_messages_proto_msgTypes[44] + mi := &file_uart_messages_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3629,7 +3719,7 @@ func (x *OtaSlaveProgressEntry) String() string { func (*OtaSlaveProgressEntry) ProtoMessage() {} func (x *OtaSlaveProgressEntry) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[44] + mi := &file_uart_messages_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3642,7 +3732,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{44} + return file_uart_messages_proto_rawDescGZIP(), []int{45} } func (x *OtaSlaveProgressEntry) GetClientId() uint32 { @@ -3693,7 +3783,7 @@ type OtaSlaveProgressResponse struct { func (x *OtaSlaveProgressResponse) Reset() { *x = OtaSlaveProgressResponse{} - mi := &file_uart_messages_proto_msgTypes[45] + mi := &file_uart_messages_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3705,7 +3795,7 @@ func (x *OtaSlaveProgressResponse) String() string { func (*OtaSlaveProgressResponse) ProtoMessage() {} func (x *OtaSlaveProgressResponse) ProtoReflect() protoreflect.Message { - mi := &file_uart_messages_proto_msgTypes[45] + mi := &file_uart_messages_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3718,7 +3808,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{45} + return file_uart_messages_proto_rawDescGZIP(), []int{46} } func (x *OtaSlaveProgressResponse) GetActive() bool { @@ -3760,7 +3850,7 @@ var File_uart_messages_proto protoreflect.FileDescriptor const file_uart_messages_proto_rawDesc = "" + "\n" + - "\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\xd3\x15\n" + + "\x13uart_messages.proto\x12\x04alox\x1a\fnanopb.proto\"\x92\x16\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" + @@ -3801,7 +3891,8 @@ const file_uart_messages_proto_rawDesc = "" + "\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\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\x11tapConfigResponseB\t\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" + "\apayload\"\x05\n" + "\x03Ack\"!\n" + "\vEchoPayload\x12\x12\n" + @@ -3843,13 +3934,14 @@ const file_uart_messages_proto_rawDesc = "" + "\bdeadzone\x18\x01 \x01(\rR\bdeadzone\x12\x1b\n" + "\tclient_id\x18\x02 \x01(\rR\bclientId\x12\x18\n" + "\asuccess\x18\x03 \x01(\bR\asuccess\x12%\n" + - "\x0eslaves_updated\x18\x04 \x01(\rR\rslavesUpdated\"\x80\x01\n" + + "\x0eslaves_updated\x18\x04 \x01(\rR\rslavesUpdated\"\x9d\x01\n" + "\x12AccelStreamRequest\x12\x14\n" + "\x05write\x18\x01 \x01(\bR\x05write\x12\x16\n" + "\x06enable\x18\x02 \x01(\bR\x06enable\x12\x1b\n" + "\tclient_id\x18\x03 \x01(\rR\bclientId\x12\x1f\n" + "\vall_clients\x18\x04 \x01(\bR\n" + - "allClients\"\x8d\x01\n" + + "allClients\x12\x1b\n" + + "\tperiod_ms\x18\x05 \x01(\rR\bperiodMs\"\x8d\x01\n" + "\x13AccelStreamResponse\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x12\x1b\n" + "\tclient_id\x18\x02 \x01(\rR\bclientId\x12\x18\n" + @@ -3915,7 +4007,11 @@ const file_uart_messages_proto_rawDesc = "" + "\x05accel\x18\x02 \x01(\v2\x16.alox.CacheClientAccelR\x05accel\x12&\n" + "\x03tap\x18\x03 \x01(\v2\x14.alox.CacheClientTapR\x03tap\"O\n" + "\x13CacheStatusResponse\x128\n" + - "\aclients\x18\x01 \x03(\v2\x17.alox.CacheClientStatusB\x05\x92?\x02\x10\x10R\aclients\"I\n" + + "\aclients\x18\x01 \x03(\v2\x17.alox.CacheClientStatusB\x05\x92?\x02\x10\x10R\aclients\"\x90\x01\n" + + "\rBleStreamTick\x12(\n" + + "\x10esp_timestamp_us\x18\x01 \x01(\x04R\x0eespTimestampUs\x12\x1b\n" + + "\tperiod_ms\x18\x02 \x01(\rR\bperiodMs\x128\n" + + "\aclients\x18\x03 \x03(\v2\x17.alox.CacheClientStatusB\x05\x92?\x02\x10\x10R\aclients\"I\n" + "\x18EspNowUnicastTestRequest\x12\x1b\n" + "\tclient_id\x18\x01 \x01(\rR\bclientId\x12\x10\n" + "\x03seq\x18\x02 \x01(\rR\x03seq\"G\n" + @@ -4032,7 +4128,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*\xaa\x03\n" + + "\x06slaves\x18\x05 \x03(\v2\x1b.alox.OtaSlaveProgressEntryB\x05\x92?\x02\x10\x10R\x06slaves*\xbf\x03\n" + "\vMessageType\x12\v\n" + "\aUNKNOWN\x10\x00\x12\a\n" + "\x03ACK\x10\x01\x12\b\n" + @@ -4060,7 +4156,8 @@ const file_uart_messages_proto_rawDesc = "" + "\x10ESPNOW_ECHO_PING\x10\x1e\x12\x11\n" + "\rSET_LOG_LEVEL\x10\x1f\x12\x0e\n" + "\n" + - "TAP_CONFIG\x10 \"\x04\b\x18\x10\x18\"\x04\b\x1c\x10\x1c*G\n" + + "TAP_CONFIG\x10 \x12\x13\n" + + "\x0fBLE_STREAM_TICK\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" + @@ -4083,7 +4180,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, 46) +var file_uart_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 47) var file_uart_messages_proto_goTypes = []any{ (MessageType)(0), // 0: alox.MessageType (TapKind)(0), // 1: alox.TapKind @@ -4112,27 +4209,28 @@ var file_uart_messages_proto_goTypes = []any{ (*CacheClientTap)(nil), // 24: alox.CacheClientTap (*CacheClientStatus)(nil), // 25: alox.CacheClientStatus (*CacheStatusResponse)(nil), // 26: alox.CacheStatusResponse - (*EspNowUnicastTestRequest)(nil), // 27: alox.EspNowUnicastTestRequest - (*EspNowUnicastTestResponse)(nil), // 28: alox.EspNowUnicastTestResponse - (*EspNowEchoPingRequest)(nil), // 29: alox.EspNowEchoPingRequest - (*EspNowEchoPingResponse)(nil), // 30: alox.EspNowEchoPingResponse - (*LedRingProgressRequest)(nil), // 31: alox.LedRingProgressRequest - (*LedRingProgressResponse)(nil), // 32: alox.LedRingProgressResponse - (*EspNowFindMeRequest)(nil), // 33: alox.EspNowFindMeRequest - (*EspNowFindMeResponse)(nil), // 34: alox.EspNowFindMeResponse - (*RestartRequest)(nil), // 35: alox.RestartRequest - (*RestartResponse)(nil), // 36: alox.RestartResponse - (*SetLogLevelRequest)(nil), // 37: alox.SetLogLevelRequest - (*SetLogLevelResponse)(nil), // 38: alox.SetLogLevelResponse - (*TapConfigRequest)(nil), // 39: alox.TapConfigRequest - (*TapConfigResponse)(nil), // 40: alox.TapConfigResponse - (*OtaStartPayload)(nil), // 41: alox.OtaStartPayload - (*OtaPayload)(nil), // 42: alox.OtaPayload - (*OtaEndPayload)(nil), // 43: alox.OtaEndPayload - (*OtaStatusPayload)(nil), // 44: alox.OtaStatusPayload - (*OtaSlaveProgressRequest)(nil), // 45: alox.OtaSlaveProgressRequest - (*OtaSlaveProgressEntry)(nil), // 46: alox.OtaSlaveProgressEntry - (*OtaSlaveProgressResponse)(nil), // 47: alox.OtaSlaveProgressResponse + (*BleStreamTick)(nil), // 27: alox.BleStreamTick + (*EspNowUnicastTestRequest)(nil), // 28: alox.EspNowUnicastTestRequest + (*EspNowUnicastTestResponse)(nil), // 29: alox.EspNowUnicastTestResponse + (*EspNowEchoPingRequest)(nil), // 30: alox.EspNowEchoPingRequest + (*EspNowEchoPingResponse)(nil), // 31: alox.EspNowEchoPingResponse + (*LedRingProgressRequest)(nil), // 32: alox.LedRingProgressRequest + (*LedRingProgressResponse)(nil), // 33: alox.LedRingProgressResponse + (*EspNowFindMeRequest)(nil), // 34: alox.EspNowFindMeRequest + (*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 } var file_uart_messages_proto_depIdxs = []int32{ 0, // 0: alox.UartMessage.type:type_name -> alox.MessageType @@ -4141,22 +4239,22 @@ 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 - 41, // 6: alox.UartMessage.ota_start:type_name -> alox.OtaStartPayload - 42, // 7: alox.UartMessage.ota_payload:type_name -> alox.OtaPayload - 43, // 8: alox.UartMessage.ota_end:type_name -> alox.OtaEndPayload - 44, // 9: alox.UartMessage.ota_status:type_name -> alox.OtaStatusPayload + 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 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 - 45, // 14: alox.UartMessage.ota_slave_progress_request:type_name -> alox.OtaSlaveProgressRequest - 47, // 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 - 34, // 19: alox.UartMessage.espnow_find_me_response:type_name -> alox.EspNowFindMeResponse - 35, // 20: alox.UartMessage.restart_request:type_name -> alox.RestartRequest - 36, // 21: alox.UartMessage.restart_response:type_name -> alox.RestartResponse + 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 + 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 + 35, // 19: alox.UartMessage.espnow_find_me_response:type_name -> alox.EspNowFindMeResponse + 36, // 20: alox.UartMessage.restart_request:type_name -> alox.RestartRequest + 37, // 21: alox.UartMessage.restart_response:type_name -> alox.RestartResponse 12, // 22: alox.UartMessage.accel_stream_request:type_name -> alox.AccelStreamRequest 13, // 23: alox.UartMessage.accel_stream_response:type_name -> alox.AccelStreamResponse 14, // 24: alox.UartMessage.battery_status_request:type_name -> alox.BatteryStatusRequest @@ -4165,28 +4263,30 @@ var file_uart_messages_proto_depIdxs = []int32{ 20, // 27: alox.UartMessage.tap_notify_response:type_name -> alox.TapNotifyResponse 22, // 28: alox.UartMessage.cache_status_request:type_name -> alox.CacheStatusRequest 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 - 37, // 32: alox.UartMessage.set_log_level_request:type_name -> alox.SetLogLevelRequest - 38, // 33: alox.UartMessage.set_log_level_response:type_name -> alox.SetLogLevelResponse - 39, // 34: alox.UartMessage.tap_config_request:type_name -> alox.TapConfigRequest - 40, // 35: alox.UartMessage.tap_config_response:type_name -> alox.TapConfigResponse - 6, // 36: alox.ClientInfoResponse.clients:type_name -> alox.ClientInfo - 8, // 37: alox.ClientInputResponse.clients:type_name -> alox.ClientInput - 15, // 38: alox.BatterySample.lipo1:type_name -> alox.LipoReading - 15, // 39: alox.BatterySample.lipo2:type_name -> alox.LipoReading - 16, // 40: alox.BatteryStatusResponse.samples:type_name -> alox.BatterySample - 1, // 41: alox.TapEvent.kind:type_name -> alox.TapKind - 1, // 42: alox.CacheClientTap.kind:type_name -> alox.TapKind - 23, // 43: alox.CacheClientStatus.accel:type_name -> alox.CacheClientAccel - 24, // 44: alox.CacheClientStatus.tap:type_name -> alox.CacheClientTap - 25, // 45: alox.CacheStatusResponse.clients:type_name -> alox.CacheClientStatus - 46, // 46: alox.OtaSlaveProgressResponse.slaves:type_name -> alox.OtaSlaveProgressEntry - 47, // [47:47] is the sub-list for method output_type - 47, // [47:47] is the sub-list for method input_type - 47, // [47:47] is the sub-list for extension type_name - 47, // [47:47] is the sub-list for extension extendee - 0, // [0:47] is the sub-list for field type_name + 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 + 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 } func init() { file_uart_messages_proto_init() } @@ -4230,6 +4330,7 @@ func file_uart_messages_proto_init() { (*UartMessage_SetLogLevelResponse)(nil), (*UartMessage_TapConfigRequest)(nil), (*UartMessage_TapConfigResponse)(nil), + (*UartMessage_BleStreamTick)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -4237,7 +4338,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: 46, + NumMessages: 47, NumExtensions: 0, NumServices: 0, }, diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 6ee231f..738c0db 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -12,6 +12,9 @@ idf_component_register( "powerpod.c" "led_ring.c" "uart.c" + "host_transport.c" + "ble_host.c" + "ble_stream.c" "uart_proto.c" "uart_cmd.c" "cmd/cmd_handler.c" @@ -59,6 +62,7 @@ idf_component_register( esp_netif esp_event nvs_flash + bt PRIV_REQUIRES esp_driver_gpio esp_driver_uart diff --git a/main/app_config.h b/main/app_config.h index 351364b..d0d3e44 100644 --- a/main/app_config.h +++ b/main/app_config.h @@ -9,6 +9,8 @@ typedef struct { bool master; uint8_t network; + /** false = UART host, true = BLE host (DIP8; mutual exclusive, boot-only). */ + bool host_ble; char running_partition[APP_RUNNING_PARTITION_LABEL_MAX]; } app_config_t; diff --git a/main/ble_host.c b/main/ble_host.c new file mode 100644 index 0000000..910b74f --- /dev/null +++ b/main/ble_host.c @@ -0,0 +1,417 @@ +#include "ble_host.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "host_transport.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_att.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" + +#include + +static const char *TAG = "[BLE_HOST]"; +static const char *DEVICE_NAME = "Powerpod"; + +#define BLE_CONN_ITVL_MIN_UNITS 6 /* 7.5 ms */ +#define BLE_CONN_ITVL_MAX_UNITS 6 +#define BLE_CONN_TUNE_MAX_TRIES 6 + +/* + * Nordic UART style: + * Service: 6e400001-b5a3-f393-e0a9-e50e24dcca9e + * Characteristic: 6e400002-b5a3-f393-e0a9-e50e24dcca9e + * Write = host→ESP (framed), Notify = ESP→host (framed). + */ +static const ble_uuid128_t ble_svc_uuid = + BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, + 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e); +static const ble_uuid128_t ble_chr_uuid = + BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, + 0xa3, 0xb5, 0x02, 0x00, 0x40, 0x6e); + +static uint8_t own_addr_type; +static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE; +static uint16_t ble_chr_val_handle; +static bool notify_enabled; +static uint16_t att_mtu = 23; +static esp_timer_handle_t conn_tune_timer; +static uint8_t conn_tune_tries; + +static int ble_gap_event(struct ble_gap_event *event, void *arg); +static void ble_request_fast_conn_params(uint16_t handle); +static void ble_optimize_link(uint16_t handle); + +bool ble_host_ready(void) { + return notify_enabled && conn_handle != BLE_HS_CONN_HANDLE_NONE; +} + +uint16_t ble_host_att_mtu(void) { return att_mtu; } + +uint16_t ble_host_max_payload(void) { + if (att_mtu < 4) { + return 20; + } + return (uint16_t)(att_mtu - 3); +} + +static esp_err_t ble_host_tx(const uint8_t *frame, size_t frame_len) { + struct os_mbuf *om; + int rc; + + if (!ble_host_ready()) { + return ESP_ERR_INVALID_STATE; + } + if (frame_len == 0 || frame_len > ble_host_max_payload()) { + return ESP_ERR_INVALID_SIZE; + } + + om = ble_hs_mbuf_from_flat(frame, (uint16_t)frame_len); + if (om == NULL) { + return ESP_ERR_NO_MEM; + } + + rc = ble_gatts_notify_custom(conn_handle, ble_chr_val_handle, om); + if (rc != 0) { + os_mbuf_free_chain(om); + return ESP_FAIL; + } + return ESP_OK; +} + +static void ble_conn_tune_timer_cb(void *arg) { + struct ble_gap_conn_desc desc; + + (void)arg; + if (conn_handle == BLE_HS_CONN_HANDLE_NONE) { + return; + } + if (ble_gap_conn_find(conn_handle, &desc) != 0) { + return; + } + if (desc.conn_itvl <= BLE_CONN_ITVL_MAX_UNITS) { + ESP_LOGI(TAG, "conn already at interval_us=%lu", + (unsigned long)desc.conn_itvl * 1250UL); + return; + } + if (conn_tune_tries >= BLE_CONN_TUNE_MAX_TRIES) { + ESP_LOGW(TAG, "central kept interval_us=%lu (ESP requested 7500)", + (unsigned long)desc.conn_itvl * 1250UL); + return; + } + + conn_tune_tries++; + ESP_LOGI(TAG, "retry fast conn params (%u/%u); current interval_us=%lu", + conn_tune_tries, BLE_CONN_TUNE_MAX_TRIES, + (unsigned long)desc.conn_itvl * 1250UL); + ble_request_fast_conn_params(conn_handle); + esp_timer_start_once(conn_tune_timer, 750 * 1000); +} + +static void ble_schedule_conn_tune(void) { + conn_tune_tries = 0; + esp_timer_stop(conn_tune_timer); + esp_timer_start_once(conn_tune_timer, 300 * 1000); +} + +static void ble_request_fast_conn_params(uint16_t handle) { + struct ble_gap_upd_params params = { + .itvl_min = BLE_CONN_ITVL_MIN_UNITS, + .itvl_max = BLE_CONN_ITVL_MAX_UNITS, + .latency = 0, + .supervision_timeout = 400, + .min_ce_len = 0, + .max_ce_len = 24, + }; + int rc = ble_gap_update_params(handle, ¶ms); + if (rc != 0) { + ESP_LOGW(TAG, "conn param update request failed: %d", rc); + } else { + ESP_LOGI(TAG, "requested conn interval 7.5 ms"); + } +} + +static void ble_optimize_link(uint16_t handle) { + int rc; + + ble_request_fast_conn_params(handle); + + rc = ble_gap_set_prefered_le_phy(handle, BLE_GAP_LE_PHY_2M_MASK, + BLE_GAP_LE_PHY_2M_MASK, + BLE_GAP_LE_PHY_CODED_ANY); + if (rc != 0) { + ESP_LOGW(TAG, "2M PHY request failed: %d", rc); + } + + rc = ble_gap_set_data_len(handle, 251, 2120); + if (rc != 0) { + ESP_LOGW(TAG, "data len request failed: %d", rc); + } + + ble_schedule_conn_tune(); +} + +static void ble_advertise(void) { + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + int rc; + + memset(&fields, 0, sizeof(fields)); + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + fields.name = (uint8_t *)DEVICE_NAME; + fields.name_len = strlen(DEVICE_NAME); + fields.name_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + ESP_LOGE(TAG, "adv set fields failed: %d", rc); + return; + } + + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + adv_params.itvl_min = 0x20; + adv_params.itvl_max = 0x30; + + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, + ble_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "adv start failed: %d", rc); + return; + } + + ESP_LOGI(TAG, "advertising as \"%s\"", DEVICE_NAME); +} + +static int ble_chr_access(uint16_t conn_handle_arg, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) { + (void)conn_handle_arg; + (void)attr_handle; + (void)arg; + + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + return 0; + + case BLE_GATT_ACCESS_OP_WRITE_CHR: { + uint16_t om_len = OS_MBUF_PKTLEN(ctxt->om); + uint8_t buf[512]; + uint16_t copied = 0; + int rc; + + if (om_len == 0 || om_len > sizeof(buf)) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + rc = ble_hs_mbuf_to_flat(ctxt->om, buf, sizeof(buf), &copied); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + + host_rx_bytes(buf, copied); + return 0; + } + + default: + return BLE_ATT_ERR_UNLIKELY; + } +} + +static const struct ble_gatt_svc_def gatt_svcs[] = { + { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &ble_svc_uuid.u, + .characteristics = + (struct ble_gatt_chr_def[]){ + { + .uuid = &ble_chr_uuid.u, + .access_cb = ble_chr_access, + .val_handle = &ble_chr_val_handle, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | + BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY, + }, + {0}, + }, + }, + {0}, +}; + +static int gatt_svr_init(void) { + int rc; + + ble_svc_gap_init(); + ble_svc_gatt_init(); + + rc = ble_gatts_count_cfg(gatt_svcs); + if (rc != 0) { + return rc; + } + + return ble_gatts_add_svcs(gatt_svcs); +} + +static int ble_gap_event(struct ble_gap_event *event, void *arg) { + (void)arg; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + ESP_LOGI(TAG, "connection %s; status=%d handle=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status, event->connect.conn_handle); + if (event->connect.status != 0) { + ble_advertise(); + } else { + conn_handle = event->connect.conn_handle; + ble_optimize_link(conn_handle); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "disconnect; reason=%d", event->disconnect.reason); + esp_timer_stop(conn_tune_timer); + conn_tune_tries = 0; + conn_handle = BLE_HS_CONN_HANDLE_NONE; + notify_enabled = false; + att_mtu = 23; + ble_advertise(); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: { + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(event->conn_update.conn_handle, &desc) == 0) { + ESP_LOGI(TAG, "conn updated; status=%d interval_us=%lu latency=%u", + event->conn_update.status, + (unsigned long)desc.conn_itvl * 1250UL, desc.conn_latency); + if (event->conn_update.status == 0 && + desc.conn_itvl > BLE_CONN_ITVL_MAX_UNITS && + conn_tune_tries < BLE_CONN_TUNE_MAX_TRIES) { + esp_timer_stop(conn_tune_timer); + esp_timer_start_once(conn_tune_timer, 500 * 1000); + } + } + return 0; + } + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + ESP_LOGI(TAG, "phy update; status=%d tx=%u rx=%u", + event->phy_updated.status, event->phy_updated.tx_phy, + event->phy_updated.rx_phy); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + ESP_LOGI(TAG, "advertise complete; reason=%d", event->adv_complete.reason); + ble_advertise(); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + if (event->subscribe.attr_handle == ble_chr_val_handle) { + notify_enabled = event->subscribe.cur_notify; + ESP_LOGI(TAG, "notify %s", notify_enabled ? "enabled" : "disabled"); + if (notify_enabled && conn_handle != BLE_HS_CONN_HANDLE_NONE) { + ble_request_fast_conn_params(conn_handle); + } + } + return 0; + + case BLE_GAP_EVENT_MTU: + att_mtu = event->mtu.value; + ESP_LOGI(TAG, "mtu update; handle=%d mtu=%d", event->mtu.conn_handle, + event->mtu.value); + return 0; + + default: + return 0; + } +} + +static void ble_on_sync(void) { + int rc; + + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "ble_hs_id_infer_auto failed: %d", rc); + return; + } + + uint8_t addr_val[6] = {0}; + ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); + ESP_LOGI(TAG, "device address: %02x:%02x:%02x:%02x:%02x:%02x", addr_val[5], + addr_val[4], addr_val[3], addr_val[2], addr_val[1], addr_val[0]); + + ble_advertise(); +} + +static void ble_on_reset(int reason) { + ESP_LOGE(TAG, "nimble reset; reason=%d", reason); +} + +static void ble_host_task(void *param) { + (void)param; + ESP_LOGI(TAG, "nimble host task started"); + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +esp_err_t init_ble_host(QueueHandle_t cmd_queue) { + esp_err_t ret; + + host_transport_set_cmd_queue(cmd_queue); + host_transport_set_tx(ble_host_tx); + + /* NVS may already be init via pod_settings; ignore ALREADY_INIT. */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || + ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "nvs_flash_init failed: %s", esp_err_to_name(ret)); + return ret; + } + + const esp_timer_create_args_t tune_args = { + .callback = &ble_conn_tune_timer_cb, + .name = "ble_conn_tune", + }; + ESP_ERROR_CHECK(esp_timer_create(&tune_args, &conn_tune_timer)); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "nimble_port_init failed: %s", esp_err_to_name(ret)); + return ret; + } + + ble_hs_cfg.sync_cb = ble_on_sync; + ble_hs_cfg.reset_cb = ble_on_reset; + + int rc = gatt_svr_init(); + if (rc != 0) { + ESP_LOGE(TAG, "gatt_svr_init failed: %d", rc); + return ESP_FAIL; + } + + rc = ble_svc_gap_device_name_set(DEVICE_NAME); + if (rc != 0) { + ESP_LOGE(TAG, "device name set failed: %d", rc); + return ESP_FAIL; + } + + rc = ble_att_set_preferred_mtu(247); + if (rc != 0) { + ESP_LOGW(TAG, "preferred MTU set failed: %d", rc); + } + + nimble_port_freertos_init(ble_host_task); + ESP_LOGI(TAG, "BLE host transport started"); + return ESP_OK; +} diff --git a/main/ble_host.h b/main/ble_host.h new file mode 100644 index 0000000..db2b66b --- /dev/null +++ b/main/ble_host.h @@ -0,0 +1,22 @@ +#ifndef BLE_HOST_H +#define BLE_HOST_H + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include +#include + +/** Start NimBLE host transport (Master only). Uses same framing as UART. */ +esp_err_t init_ble_host(QueueHandle_t cmd_queue); + +/** True when a central is connected and CCCD notify is enabled. */ +bool ble_host_ready(void); + +/** Negotiated ATT MTU (default 23 until exchange). */ +uint16_t ble_host_att_mtu(void); + +/** Max notify/write payload bytes (ATT MTU - 3). */ +uint16_t ble_host_max_payload(void); + +#endif diff --git a/main/ble_stream.c b/main/ble_stream.c new file mode 100644 index 0000000..fc66f37 --- /dev/null +++ b/main/ble_stream.c @@ -0,0 +1,201 @@ +#include "ble_stream.h" +#include "app_config.h" +#include "ble_host.h" +#include "client_registry.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "pb_encode.h" +#include "uart.h" +#include "uart_cmd.h" +#include "uart_messages.pb.h" +#include + +static const char *TAG = "[BLE_STREAM]"; + +#define BLE_STREAM_PERIOD_MIN_MS 8u +#define BLE_STREAM_PERIOD_MAX_MS 16u +#define BLE_STREAM_PERIOD_DEFAULT_MS 16u + +static uint32_t s_period_ms = BLE_STREAM_PERIOD_DEFAULT_MS; +static TaskHandle_t s_task; +static volatile bool s_running; + +static bool tap_notify_any(const client_info_t *client) { + return client != NULL && + (client->tap_notify_single || client->tap_notify_double || + client->tap_notify_triple); +} + +static alox_TapKind tap_kind_from_registry(uint32_t kind) { + switch (kind) { + case 1: + return alox_TapKind_TAP_SINGLE; + case 2: + return alox_TapKind_TAP_DOUBLE; + case 3: + return alox_TapKind_TAP_TRIPLE; + default: + return alox_TapKind_TAP_NONE; + } +} + +static void fill_clients(alox_BleStreamTick *tick) { + tick->clients_count = 0; + size_t count = client_registry_count(); + for (size_t i = 0; i < count; i++) { + const client_info_t *client = client_registry_at(i); + if (client == NULL) { + continue; + } + const bool want_accel = client->accel_stream_enabled; + const bool want_tap = tap_notify_any(client); + if (!want_accel && !want_tap) { + continue; + } + if (tick->clients_count >= + sizeof(tick->clients) / sizeof(tick->clients[0])) { + break; + } + + alox_CacheClientStatus *entry = &tick->clients[tick->clients_count++]; + entry->client_id = client->id; + entry->has_accel = false; + entry->has_tap = false; + + if (want_accel) { + entry->has_accel = true; + entry->accel.valid = client->accel_valid; + if (client->accel_valid) { + entry->accel.x = client->accel_x; + entry->accel.y = client->accel_y; + entry->accel.z = client->accel_z; + entry->accel.age_ms = + client_registry_ms_since(client->accel_updated_at); + } + } + + if (want_tap) { + uint32_t kind = 0; + uint32_t age_ms = 0; + if (client_registry_take_tap(client->id, &kind, &age_ms)) { + entry->has_tap = true; + entry->tap.kind = tap_kind_from_registry(kind); + entry->tap.age_ms = age_ms; + } + } + } +} + +static bool any_accel_stream(void) { + size_t count = client_registry_count(); + for (size_t i = 0; i < count; i++) { + const client_info_t *client = client_registry_at(i); + if (client != NULL && client->accel_stream_enabled) { + return true; + } + } + return false; +} + +static esp_err_t send_tick_truncated(alox_UartMessage *msg) { + uint8_t pb_buf[MAX_PAYLOAD_SIZE]; + pb_ostream_t stream = pb_ostream_from_buffer(pb_buf, sizeof(pb_buf)); + + while (true) { + stream = pb_ostream_from_buffer(pb_buf, sizeof(pb_buf)); + if (!pb_encode(&stream, alox_UartMessage_fields, msg)) { + return ESP_FAIL; + } + + size_t payload_len = stream.bytes_written + 1; + uint16_t max_pay = ble_host_max_payload(); + /* Framed size = payload + 4; notify must fit ATT MTU-3. */ + size_t frame_len = payload_len + 4; + if (payload_len <= MAX_PAYLOAD_SIZE && frame_len <= max_pay) { + uint8_t payload[MAX_PAYLOAD_SIZE]; + payload[0] = (uint8_t)msg->type; + memcpy(&payload[1], pb_buf, stream.bytes_written); + return uart_send_framed(payload, payload_len); + } + + if (msg->payload.ble_stream_tick.clients_count == 0) { + ESP_LOGW(TAG, "tick too large even with 0 clients (need %u max %u)", + (unsigned)frame_len, (unsigned)max_pay); + return ESP_ERR_NO_MEM; + } + + msg->payload.ble_stream_tick.clients_count--; + ESP_LOGW(TAG, "truncating tick clients to %u for MTU", + (unsigned)msg->payload.ble_stream_tick.clients_count); + } +} + +static void ble_stream_task(void *param) { + (void)param; + ESP_LOGI(TAG, "BLE stream task started period_ms=%lu", + (unsigned long)s_period_ms); + + while (s_running) { + uint32_t period = s_period_ms; + if (period < BLE_STREAM_PERIOD_MIN_MS) { + period = BLE_STREAM_PERIOD_MIN_MS; + } + if (period > BLE_STREAM_PERIOD_MAX_MS) { + period = BLE_STREAM_PERIOD_MAX_MS; + } + + if (ble_host_ready() && any_accel_stream()) { + alox_UartMessage msg = alox_UartMessage_init_zero; + uart_cmd_init_response(&msg, alox_MessageType_BLE_STREAM_TICK, + alox_UartMessage_ble_stream_tick_tag); + msg.payload.ble_stream_tick.esp_timestamp_us = + (uint64_t)esp_timer_get_time(); + msg.payload.ble_stream_tick.period_ms = period; + fill_clients(&msg.payload.ble_stream_tick); + + esp_err_t err = send_tick_truncated(&msg); + if (err != ESP_OK) { + ESP_LOGW(TAG, "tick send failed: %s", esp_err_to_name(err)); + } + } + + vTaskDelay(pdMS_TO_TICKS(period)); + } + + s_task = NULL; + vTaskDelete(NULL); +} + +void ble_stream_set_period_ms(uint32_t period_ms) { + if (period_ms == 0) { + period_ms = BLE_STREAM_PERIOD_DEFAULT_MS; + } + if (period_ms < BLE_STREAM_PERIOD_MIN_MS) { + period_ms = BLE_STREAM_PERIOD_MIN_MS; + } + if (period_ms > BLE_STREAM_PERIOD_MAX_MS) { + period_ms = BLE_STREAM_PERIOD_MAX_MS; + } + s_period_ms = period_ms; +} + +uint32_t ble_stream_period_ms(void) { return s_period_ms; } + +void ble_stream_update(void) { + const app_config_t *cfg = app_config_get(); + bool want = cfg != NULL && cfg->host_ble && any_accel_stream(); + + if (want && !s_running) { + s_running = true; + if (xTaskCreate(ble_stream_task, "ble_stream", 4096, NULL, 5, &s_task) != + pdPASS) { + s_running = false; + s_task = NULL; + ESP_LOGE(TAG, "failed to create ble_stream task"); + } + } else if (!want && s_running) { + s_running = false; + } +} diff --git a/main/ble_stream.h b/main/ble_stream.h new file mode 100644 index 0000000..3effab5 --- /dev/null +++ b/main/ble_stream.h @@ -0,0 +1,18 @@ +#ifndef BLE_STREAM_H +#define BLE_STREAM_H + +#include +#include + +/** Clamp and store BLE push period (8–16 ms). */ +void ble_stream_set_period_ms(uint32_t period_ms); + +uint32_t ble_stream_period_ms(void); + +/** + * Re-evaluate whether the BLE push task should run (host_ble + any accel + * stream enabled). Safe to call from accel-stream handlers. + */ +void ble_stream_update(void); + +#endif diff --git a/main/cmd/cmd_accel_stream.c b/main/cmd/cmd_accel_stream.c index 9cefb1a..43cc51a 100644 --- a/main/cmd/cmd_accel_stream.c +++ b/main/cmd/cmd_accel_stream.c @@ -1,5 +1,6 @@ #include "client_registry.h" #include "cmd_accel_stream.h" +#include "ble_stream.h" #include "esp_log.h" #include "esp_now_comm.h" #include "uart_cmd.h" @@ -50,6 +51,10 @@ static void handle_accel_stream(const uint8_t *data, size_t len) { } if (req.write) { + if (req.period_ms != 0) { + ble_stream_set_period_ms(req.period_ms); + } + if (req.all_clients) { size_t n = client_registry_set_accel_stream_all(req.enable); uint32_t sent = 0; @@ -65,8 +70,10 @@ static void handle_accel_stream(const uint8_t *data, size_t len) { } } - ESP_LOGI(TAG, "accel stream %s for %u/%u slaves", - req.enable ? "on" : "off", (unsigned)sent, (unsigned)n); + ESP_LOGI(TAG, "accel stream %s for %u/%u slaves period_ms=%lu", + req.enable ? "on" : "off", (unsigned)sent, (unsigned)n, + (unsigned long)ble_stream_period_ms()); + ble_stream_update(); reply(req.enable, 0, sent > 0, sent); return; } @@ -85,6 +92,7 @@ static void handle_accel_stream(const uint8_t *data, size_t len) { } esp_err_t err = push_stream_to_slave(client, req.enable); + ble_stream_update(); reply(req.enable, req.client_id, err == ESP_OK, err == ESP_OK ? 1u : 0u); return; } diff --git a/main/cmd/cmd_ota.c b/main/cmd/cmd_ota.c index 5146081..ce1209c 100644 --- a/main/cmd/cmd_ota.c +++ b/main/cmd/cmd_ota.c @@ -1,4 +1,5 @@ #include "cmd_ota.h" +#include "app_config.h" #include "led_ring.h" #include "ota_espnow.h" #include "ota_uart.h" @@ -103,10 +104,24 @@ static void ota_prepare_task(void *param) { vTaskDelete(NULL); } +static bool ota_host_is_uart(void) { + const app_config_t *cfg = app_config_get(); + if (cfg != NULL && cfg->host_ble) { + ESP_LOGW(TAG, "OTA requires UART host (DIP8=0); BLE host active"); + return false; + } + return true; +} + static void handle_ota_start(const uint8_t *data, size_t len) { alox_UartMessage uart_msg; alox_OtaStartPayload req = alox_OtaStartPayload_init_zero; + if (!ota_host_is_uart()) { + send_ota_failed(20); + return; + } + if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) { send_ota_failed( 2); return; @@ -334,6 +349,10 @@ static void ota_start_espnow_task(void *param) { } static void handle_ota_start_espnow(const uint8_t *data, size_t len) { + if (!ota_host_is_uart()) { + send_ota_failed(20); + return; + } (void)data; (void)len; diff --git a/main/host_transport.c b/main/host_transport.c new file mode 100644 index 0000000..a0853d9 --- /dev/null +++ b/main/host_transport.c @@ -0,0 +1,97 @@ +#include "host_transport.h" +#include "cmd_handler.h" +#include "esp_log.h" +#include "uart.h" +#include +#include + +static const char *TAG = "[HOST]"; + +static host_tx_fn_t s_tx_fn; +static QueueHandle_t s_cmd_queue; +static uart_packet_t s_rx_packet = {.state = STATE_START}; +static TickType_t s_last_byte_ticks; +static const TickType_t s_rx_timeout_ticks = pdMS_TO_TICKS(50); + +void host_transport_set_tx(host_tx_fn_t tx_fn) { s_tx_fn = tx_fn; } + +void host_transport_set_cmd_queue(QueueHandle_t cmd_queue) { + s_cmd_queue = cmd_queue; +} + +QueueHandle_t host_transport_cmd_queue(void) { return s_cmd_queue; } + +bool host_enqueue_packet(const uint8_t *payload, uint8_t len) { + if (s_cmd_queue == NULL || payload == NULL || len == 0) { + return false; + } + + generic_msg_t msg = { + .msg_id = payload[0], + .len = len > 1 ? (size_t)(len - 1) : 0, + .payload = NULL, + }; + + if (msg.len > 0) { + msg.payload = malloc(msg.len); + if (msg.payload == NULL) { + ESP_LOGE(TAG, "failed to allocate command payload"); + return false; + } + memcpy(msg.payload, &payload[1], msg.len); + } + + if (xQueueSend(s_cmd_queue, &msg, pdMS_TO_TICKS(500)) != pdPASS) { + free(msg.payload); + ESP_LOGW(TAG, "command queue full (cmd 0x%02x)", (unsigned)msg.msg_id); + return false; + } + return true; +} + +esp_err_t host_send_framed(const uint8_t *payload, size_t len) { + if (payload == NULL || len == 0 || len > MAX_PAYLOAD_SIZE) { + return ESP_ERR_INVALID_ARG; + } + if (s_tx_fn == NULL) { + return ESP_ERR_INVALID_STATE; + } + + uint8_t checksum = 0; + for (size_t i = 0; i < len; i++) { + checksum ^= payload[i]; + } + + uint8_t frame[4 + MAX_PAYLOAD_SIZE]; + size_t pos = 0; + frame[pos++] = START_MARKER; + frame[pos++] = (uint8_t)len; + memcpy(&frame[pos], payload, len); + pos += len; + frame[pos++] = checksum; + frame[pos++] = STOP_MARKER; + + return s_tx_fn(frame, pos); +} + +void host_rx_bytes(const uint8_t *data, size_t len) { + if (data == NULL || len == 0) { + return; + } + + TickType_t now = xTaskGetTickCount(); + if (s_rx_packet.state != STATE_START && + (now - s_last_byte_ticks) > s_rx_timeout_ticks) { + s_rx_packet.state = STATE_START; + } + + for (size_t i = 0; i < len; i++) { + if (parse_uart_byte(data[i], &s_rx_packet)) { + ESP_LOGI(TAG, "received message cmd=0x%02x len=%u", + s_rx_packet.len > 0 ? s_rx_packet.payload[0] : 0, + (unsigned)s_rx_packet.len); + host_enqueue_packet(s_rx_packet.payload, s_rx_packet.len); + } + } + s_last_byte_ticks = now; +} diff --git a/main/host_transport.h b/main/host_transport.h new file mode 100644 index 0000000..269683a --- /dev/null +++ b/main/host_transport.h @@ -0,0 +1,36 @@ +#ifndef HOST_TRANSPORT_H +#define HOST_TRANSPORT_H + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include +#include + +/** TX callback: send a complete framed packet (0xAA … 0xCC). */ +typedef esp_err_t (*host_tx_fn_t)(const uint8_t *frame, size_t frame_len); + +/** Set the active host TX backend (UART write or BLE notify). */ +void host_transport_set_tx(host_tx_fn_t tx_fn); + +/** Bind the shared cmd_queue used by uart_enqueue_packet / host RX. */ +void host_transport_set_cmd_queue(QueueHandle_t cmd_queue); + +QueueHandle_t host_transport_cmd_queue(void); + +/** + * Build AA|len|payload|xor|CC and send via the registered TX backend. + * Same semantics as uart_send_framed. + */ +esp_err_t host_send_framed(const uint8_t *payload, size_t len); + +/** + * Feed raw host RX bytes into the shared frame parser and enqueue complete + * packets onto the cmd_queue. + */ +void host_rx_bytes(const uint8_t *data, size_t len); + +/** Enqueue a completed uart_packet_t (payload[0]=msg type). */ +bool host_enqueue_packet(const uint8_t *payload, uint8_t len); + +#endif diff --git a/main/powerpod.c b/main/powerpod.c index b2b9987..32cd55c 100644 --- a/main/powerpod.c +++ b/main/powerpod.c @@ -32,8 +32,10 @@ #include "led_ring.h" #include "pod_settings.h" #include "uart.h" +#include "ble_host.h" #include "battery_uv.h" #include +#include enum MASTER_STATES { MSTATE_INIT, @@ -94,7 +96,8 @@ void app_main(void) { gpio_reset_pin(DIP_MASTER); gpio_set_direction(DIP_MASTER, GPIO_MODE_INPUT); uint8_t master = gpio_get_level(DIP_MASTER) ? 0 : 1; - uint8_t network = 0; + uint8_t network = 1; + bool host_ble = false; esp_err_t err; @@ -137,12 +140,20 @@ void app_main(void) { -1); if (err != ESP_OK) { ESP_LOGI(TAG, "Could not read IO Expander input %d", err); + } else { + /* High nibble physically reversed on PCB; DIP1..8 after reverse+invert. */ + uint8_t bits = (uint8_t)(~reverse_high_nibble_lut(read_val)); + /* DIP8 (bit7) = host transport: 1 = BLE, 0 = UART */ + host_ble = (bits & 0x80u) != 0; + uint8_t net_bits = (uint8_t)(bits & 0x7Fu); + if (net_bits == 0 || (net_bits & (net_bits - 1u)) != 0) { + ESP_LOGW(TAG, "invalid network DIP one-hot=0x%02x; fallback network=1", + net_bits); + network = 1; + } else { + network = (uint8_t)(__builtin_ctz(net_bits) + 1); /* 1..7 */ + } } - // Build NetworkNumber because Pin 5-8 are reversed - network = reverse_high_nibble_lut(read_val); - network = ~network; // reverse bits - network = __builtin_ctz(network) + - 1; // map network to 0-8, 0 is not a valid network } if (bus_handle != NULL) { @@ -167,6 +178,7 @@ void app_main(void) { app_config.master = (master != 0); app_config.network = network; + app_config.host_ble = host_ble; if (running != NULL) { memcpy(app_config.running_partition, running->label, sizeof(app_config.running_partition)); @@ -177,6 +189,8 @@ void app_main(void) { ESP_LOGI(TAG, "RUNNING CONFIG:"); ESP_LOGI(TAG, "Master: %d", app_config.master); ESP_LOGI(TAG, "Network: %d", app_config.network); + ESP_LOGI(TAG, "Host: %s (DIP8; reboot to switch)", + app_config.host_ble ? "BLE" : "UART"); ESP_LOGI(TAG, "Running Partition: %s (OTA slot %d)", app_config.running_partition, ota_slot); @@ -193,7 +207,14 @@ void app_main(void) { if (app_config.master) { cmd_queue = xQueueCreate(64, sizeof(generic_msg_t)); init_cmdHandler(cmd_queue); - init_uart(cmd_queue); + if (app_config.host_ble) { + err = init_ble_host(cmd_queue); + if (err != ESP_OK) { + ESP_LOGE(TAG, "BLE host init failed: %s", esp_err_to_name(err)); + } + } else { + init_uart(cmd_queue); + } cmd_version_register(); cmd_client_info_register(); cmd_accel_deadzone_register(); diff --git a/main/proto/uart_messages.pb.c b/main/proto/uart_messages.pb.c index 75eb80a..245104f 100644 --- a/main/proto/uart_messages.pb.c +++ b/main/proto/uart_messages.pb.c @@ -81,6 +81,9 @@ PB_BIND(alox_CacheClientStatus, alox_CacheClientStatus, AUTO) PB_BIND(alox_CacheStatusResponse, alox_CacheStatusResponse, 2) +PB_BIND(alox_BleStreamTick, alox_BleStreamTick, 2) + + PB_BIND(alox_EspNowUnicastTestRequest, alox_EspNowUnicastTestRequest, AUTO) diff --git a/main/proto/uart_messages.pb.h b/main/proto/uart_messages.pb.h index 53c7f16..440d953 100644 --- a/main/proto/uart_messages.pb.h +++ b/main/proto/uart_messages.pb.h @@ -38,7 +38,9 @@ typedef enum _alox_MessageType { /* * Host → master: get/set ESP-IDF log level for tag "*" (global). */ alox_MessageType_SET_LOG_LEVEL = 31, /* * Host → master: get/set BMA456H multitap detection parameters. */ - alox_MessageType_TAP_CONFIG = 32 + alox_MessageType_TAP_CONFIG = 32, + /* * Master → host (BLE only): periodic pushed cache snapshot with ESP timestamp. */ + alox_MessageType_BLE_STREAM_TICK = 33 } alox_MessageType; typedef enum _alox_TapKind { @@ -113,12 +115,14 @@ typedef struct _alox_AccelDeadzoneResponse { } alox_AccelDeadzoneResponse; /* Host → master: enable/disable slave accel ESP-NOW stream (~16 ms per slave). - write=false: read; write=true: apply. client_id 0 invalid for write (use >0 or all_clients). */ + write=false: read; write=true: apply. client_id 0 invalid for write (use >0 or all_clients). + period_ms (8–16): BLE host push interval for BleStreamTick; ignored on UART (default 16). */ typedef struct _alox_AccelStreamRequest { bool write; bool enable; uint32_t client_id; bool all_clients; + uint32_t period_ms; } alox_AccelStreamRequest; typedef struct _alox_AccelStreamResponse { @@ -231,6 +235,14 @@ typedef struct _alox_CacheStatusResponse { alox_CacheClientStatus clients[16]; } alox_CacheStatusResponse; +/* * Master → host over BLE notify: aggregated cache + ESP timer timestamp. */ +typedef struct _alox_BleStreamTick { + uint64_t esp_timestamp_us; + uint32_t period_ms; + pb_size_t clients_count; + alox_CacheClientStatus clients[16]; +} alox_BleStreamTick; + typedef struct _alox_EspNowUnicastTestRequest { uint32_t client_id; uint32_t seq; @@ -443,6 +455,7 @@ typedef struct _alox_UartMessage { alox_SetLogLevelResponse set_log_level_response; alox_TapConfigRequest tap_config_request; alox_TapConfigResponse tap_config_response; + alox_BleStreamTick ble_stream_tick; } payload; } alox_UartMessage; @@ -453,8 +466,8 @@ extern "C" { /* Helper constants for enums */ #define _alox_MessageType_MIN alox_MessageType_UNKNOWN -#define _alox_MessageType_MAX alox_MessageType_TAP_CONFIG -#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_TAP_CONFIG+1)) +#define _alox_MessageType_MAX alox_MessageType_BLE_STREAM_TICK +#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_BLE_STREAM_TICK+1)) #define _alox_TapKind_MIN alox_TapKind_TAP_NONE #define _alox_TapKind_MAX alox_TapKind_TAP_TRIPLE @@ -508,6 +521,7 @@ extern "C" { + /* Initializer values for message structs */ @@ -521,7 +535,7 @@ extern "C" { #define alox_ClientInputResponse_init_default {{{NULL}, NULL}} #define alox_AccelDeadzoneRequest_init_default {0, 0, 0, 0} #define alox_AccelDeadzoneResponse_init_default {0, 0, 0, 0} -#define alox_AccelStreamRequest_init_default {0, 0, 0, 0} +#define alox_AccelStreamRequest_init_default {0, 0, 0, 0, 0} #define alox_AccelStreamResponse_init_default {0, 0, 0, 0} #define alox_BatteryStatusRequest_init_default {0, 0} #define alox_LipoReading_init_default {0, 0} @@ -536,6 +550,7 @@ extern "C" { #define alox_CacheClientTap_init_default {_alox_TapKind_MIN, 0} #define alox_CacheClientStatus_init_default {0, false, alox_CacheClientAccel_init_default, false, alox_CacheClientTap_init_default} #define alox_CacheStatusResponse_init_default {0, {alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default}} +#define alox_BleStreamTick_init_default {0, 0, 0, {alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default, alox_CacheClientStatus_init_default}} #define alox_EspNowUnicastTestRequest_init_default {0, 0} #define alox_EspNowUnicastTestResponse_init_default {0, 0} #define alox_EspNowEchoPingRequest_init_default {0, 0} @@ -567,7 +582,7 @@ extern "C" { #define alox_ClientInputResponse_init_zero {{{NULL}, NULL}} #define alox_AccelDeadzoneRequest_init_zero {0, 0, 0, 0} #define alox_AccelDeadzoneResponse_init_zero {0, 0, 0, 0} -#define alox_AccelStreamRequest_init_zero {0, 0, 0, 0} +#define alox_AccelStreamRequest_init_zero {0, 0, 0, 0, 0} #define alox_AccelStreamResponse_init_zero {0, 0, 0, 0} #define alox_BatteryStatusRequest_init_zero {0, 0} #define alox_LipoReading_init_zero {0, 0} @@ -582,6 +597,7 @@ extern "C" { #define alox_CacheClientTap_init_zero {_alox_TapKind_MIN, 0} #define alox_CacheClientStatus_init_zero {0, false, alox_CacheClientAccel_init_zero, false, alox_CacheClientTap_init_zero} #define alox_CacheStatusResponse_init_zero {0, {alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero}} +#define alox_BleStreamTick_init_zero {0, 0, 0, {alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero, alox_CacheClientStatus_init_zero}} #define alox_EspNowUnicastTestRequest_init_zero {0, 0} #define alox_EspNowUnicastTestResponse_init_zero {0, 0} #define alox_EspNowEchoPingRequest_init_zero {0, 0} @@ -638,6 +654,7 @@ extern "C" { #define alox_AccelStreamRequest_enable_tag 2 #define alox_AccelStreamRequest_client_id_tag 3 #define alox_AccelStreamRequest_all_clients_tag 4 +#define alox_AccelStreamRequest_period_ms_tag 5 #define alox_AccelStreamResponse_enabled_tag 1 #define alox_AccelStreamResponse_client_id_tag 2 #define alox_AccelStreamResponse_success_tag 3 @@ -685,6 +702,9 @@ extern "C" { #define alox_CacheClientStatus_accel_tag 2 #define alox_CacheClientStatus_tap_tag 3 #define alox_CacheStatusResponse_clients_tag 1 +#define alox_BleStreamTick_esp_timestamp_us_tag 1 +#define alox_BleStreamTick_period_ms_tag 2 +#define alox_BleStreamTick_clients_tag 3 #define alox_EspNowUnicastTestRequest_client_id_tag 1 #define alox_EspNowUnicastTestRequest_seq_tag 2 #define alox_EspNowUnicastTestResponse_success_tag 1 @@ -801,6 +821,7 @@ extern "C" { #define alox_UartMessage_set_log_level_response_tag 38 #define alox_UartMessage_tap_config_request_tag 39 #define alox_UartMessage_tap_config_response_tag 40 +#define alox_UartMessage_ble_stream_tick_tag 41 /* Struct field encoding specification for nanopb */ #define alox_UartMessage_FIELDLIST(X, a) \ @@ -839,7 +860,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_echo_ping_response,payload.es 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) \ 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,tap_config_response,payload.tap_config_response), 40) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,ble_stream_tick,payload.ble_stream_tick), 41) #define alox_UartMessage_CALLBACK NULL #define alox_UartMessage_DEFAULT NULL #define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack @@ -877,6 +899,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,tap_config_response,payload.tap_conf #define alox_UartMessage_payload_set_log_level_response_MSGTYPE alox_SetLogLevelResponse #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_Ack_FIELDLIST(X, a) \ @@ -950,7 +973,8 @@ X(a, STATIC, SINGULAR, UINT32, slaves_updated, 4) X(a, STATIC, SINGULAR, BOOL, write, 1) \ X(a, STATIC, SINGULAR, BOOL, enable, 2) \ X(a, STATIC, SINGULAR, UINT32, client_id, 3) \ -X(a, STATIC, SINGULAR, BOOL, all_clients, 4) +X(a, STATIC, SINGULAR, BOOL, all_clients, 4) \ +X(a, STATIC, SINGULAR, UINT32, period_ms, 5) #define alox_AccelStreamRequest_CALLBACK NULL #define alox_AccelStreamRequest_DEFAULT NULL @@ -1064,6 +1088,14 @@ X(a, STATIC, REPEATED, MESSAGE, clients, 1) #define alox_CacheStatusResponse_DEFAULT NULL #define alox_CacheStatusResponse_clients_MSGTYPE alox_CacheClientStatus +#define alox_BleStreamTick_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT64, esp_timestamp_us, 1) \ +X(a, STATIC, SINGULAR, UINT32, period_ms, 2) \ +X(a, STATIC, REPEATED, MESSAGE, clients, 3) +#define alox_BleStreamTick_CALLBACK NULL +#define alox_BleStreamTick_DEFAULT NULL +#define alox_BleStreamTick_clients_MSGTYPE alox_CacheClientStatus + #define alox_EspNowUnicastTestRequest_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, client_id, 1) \ X(a, STATIC, SINGULAR, UINT32, seq, 2) @@ -1255,6 +1287,7 @@ extern const pb_msgdesc_t alox_CacheClientAccel_msg; extern const pb_msgdesc_t alox_CacheClientTap_msg; extern const pb_msgdesc_t alox_CacheClientStatus_msg; extern const pb_msgdesc_t alox_CacheStatusResponse_msg; +extern const pb_msgdesc_t alox_BleStreamTick_msg; extern const pb_msgdesc_t alox_EspNowUnicastTestRequest_msg; extern const pb_msgdesc_t alox_EspNowUnicastTestResponse_msg; extern const pb_msgdesc_t alox_EspNowEchoPingRequest_msg; @@ -1303,6 +1336,7 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg; #define alox_CacheClientTap_fields &alox_CacheClientTap_msg #define alox_CacheClientStatus_fields &alox_CacheClientStatus_msg #define alox_CacheStatusResponse_fields &alox_CacheStatusResponse_msg +#define alox_BleStreamTick_fields &alox_BleStreamTick_msg #define alox_EspNowUnicastTestRequest_fields &alox_EspNowUnicastTestRequest_msg #define alox_EspNowUnicastTestResponse_fields &alox_EspNowUnicastTestResponse_msg #define alox_EspNowEchoPingRequest_fields &alox_EspNowEchoPingRequest_msg @@ -1332,16 +1366,17 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg; /* alox_ClientInfo_size depends on runtime parameters */ /* alox_ClientInfoResponse_size depends on runtime parameters */ /* alox_ClientInputResponse_size depends on runtime parameters */ -#define ALOX_UART_MESSAGES_PB_H_MAX_SIZE alox_CacheStatusResponse_size +#define ALOX_UART_MESSAGES_PB_H_MAX_SIZE alox_BleStreamTick_size #define alox_AccelDeadzoneRequest_size 16 #define alox_AccelDeadzoneResponse_size 20 #define alox_AccelSample_size 32 -#define alox_AccelStreamRequest_size 12 +#define alox_AccelStreamRequest_size 18 #define alox_AccelStreamResponse_size 16 #define alox_Ack_size 0 #define alox_BatterySample_size 32 #define alox_BatteryStatusRequest_size 8 #define alox_BatteryStatusResponse_size 580 +#define alox_BleStreamTick_size 753 #define alox_CacheClientAccel_size 26 #define alox_CacheClientStatus_size 44 #define alox_CacheClientTap_size 8 diff --git a/main/proto/uart_messages.proto b/main/proto/uart_messages.proto index e00fe19..ef112d4 100644 --- a/main/proto/uart_messages.proto +++ b/main/proto/uart_messages.proto @@ -35,6 +35,8 @@ enum MessageType { SET_LOG_LEVEL = 31; /** Host → master: get/set BMA456H multitap detection parameters. */ TAP_CONFIG = 32; + /** Master → host (BLE only): periodic pushed cache snapshot with ESP timestamp. */ + BLE_STREAM_TICK = 33; } message UartMessage { @@ -75,6 +77,7 @@ message UartMessage { SetLogLevelResponse set_log_level_response = 38; TapConfigRequest tap_config_request = 39; TapConfigResponse tap_config_response = 40; + BleStreamTick ble_stream_tick = 41; } } @@ -141,11 +144,13 @@ message AccelDeadzoneResponse { // Host → master: enable/disable slave accel ESP-NOW stream (~16 ms per slave). // write=false: read; write=true: apply. client_id 0 invalid for write (use >0 or all_clients). +// period_ms (8–16): BLE host push interval for BleStreamTick; ignored on UART (default 16). message AccelStreamRequest { bool write = 1; bool enable = 2; uint32 client_id = 3; bool all_clients = 4; + uint32 period_ms = 5; } message AccelStreamResponse { @@ -257,6 +262,13 @@ message CacheStatusResponse { repeated CacheClientStatus clients = 1 [(nanopb).max_count = 16]; } +/** Master → host over BLE notify: aggregated cache + ESP timer timestamp. */ +message BleStreamTick { + uint64 esp_timestamp_us = 1; + uint32 period_ms = 2; + repeated CacheClientStatus clients = 3 [(nanopb).max_count = 16]; +} + message EspNowUnicastTestRequest { uint32 client_id = 1; uint32 seq = 2; diff --git a/main/uart.c b/main/uart.c index d98748d..3179c07 100644 --- a/main/uart.c +++ b/main/uart.c @@ -4,46 +4,28 @@ #include "esp_log.h" #include "freertos/idf_additions.h" #include "hal/uart_types.h" +#include "host_transport.h" #include "uart.h" #include #include #include static const char *TAG = "[UART]"; -static QueueHandle_t uart_cmd_queue; -static bool uart_enqueue_packet(const uart_packet_t *packet) { - if (packet->len == 0) { - return false; +static esp_err_t uart_host_tx(const uint8_t *frame, size_t frame_len) { + int written = uart_write_bytes(UART_NUM, frame, frame_len); + if (written < 0 || (size_t)written != frame_len) { + return ESP_FAIL; } - - generic_msg_t msg = { - .msg_id = packet->payload[0], - .len = packet->len > 1 ? packet->len - 1 : 0, - .payload = NULL, - }; - - if (msg.len > 0) { - msg.payload = malloc(msg.len); - if (msg.payload == NULL) { - ESP_LOGE(TAG, "failed to allocate command payload"); - return false; - } - memcpy(msg.payload, &packet->payload[1], msg.len); - } - - if (xQueueSend(uart_cmd_queue, &msg, pdMS_TO_TICKS(500)) != pdPASS) { - free(msg.payload); - ESP_LOGW(TAG, "command queue full (cmd 0x%02x)", (unsigned)msg.msg_id); - return false; - } - return true; + return ESP_OK; } void init_uart(QueueHandle_t cmd_queue) { esp_err_t err; - uart_cmd_queue = cmd_queue; + host_transport_set_cmd_queue(cmd_queue); + host_transport_set_tx(uart_host_tx); + uart_config_t uart_config = { .baud_rate = UART_BAUD_RATE, .data_bits = UART_DATA_8_BITS, @@ -85,27 +67,10 @@ void uart_read_task(void *param) { return; } - int len = 0; - uart_packet_t packet = {.state = STATE_START}; - TickType_t last_byte_time = xTaskGetTickCount(); - const TickType_t timeout_ticks = pdMS_TO_TICKS(50); - while (1) { - len = uart_read_bytes(UART_NUM, data, UART_BUF_SIZE, pdMS_TO_TICKS(20)); + int len = uart_read_bytes(UART_NUM, data, UART_BUF_SIZE, pdMS_TO_TICKS(20)); if (len > 0) { - for (int i = 0; i < len; ++i) { - if (parse_uart_byte(data[i], &packet)) { - ESP_LOGI(TAG, "received message cmd=0x%02x len=%u", - packet.len > 0 ? packet.payload[0] : 0, (unsigned)packet.len); - uart_enqueue_packet(&packet); - } - } - last_byte_time = xTaskGetTickCount(); - } else if (packet.state != STATE_START) { - TickType_t now = xTaskGetTickCount(); - if ((now - last_byte_time) > timeout_ticks) { - packet.state = STATE_START; - } + host_rx_bytes(data, (size_t)len); } } } @@ -156,27 +121,5 @@ bool parse_uart_byte(uint8_t byte, uart_packet_t *p) { } esp_err_t uart_send_framed(const uint8_t *payload, size_t len) { - if (payload == NULL || len == 0 || len > MAX_PAYLOAD_SIZE) { - return ESP_ERR_INVALID_ARG; - } - - uint8_t checksum = 0; - for (size_t i = 0; i < len; i++) { - checksum ^= payload[i]; - } - - uint8_t frame[4 + MAX_PAYLOAD_SIZE]; - size_t pos = 0; - frame[pos++] = START_MARKER; - frame[pos++] = (uint8_t)len; - memcpy(&frame[pos], payload, len); - pos += len; - frame[pos++] = checksum; - frame[pos++] = STOP_MARKER; - - int written = uart_write_bytes(UART_NUM, frame, pos); - if (written < 0 || (size_t)written != pos) { - return ESP_FAIL; - } - return ESP_OK; + return host_send_framed(payload, len); } diff --git a/sdkconfig b/sdkconfig index ac9d09b..f4c0e84 100644 --- a/sdkconfig +++ b/sdkconfig @@ -650,16 +650,296 @@ CONFIG_APPTRACE_LOCK_ENABLE=y # # Bluetooth # -# CONFIG_BT_ENABLED is not set +CONFIG_BT_ENABLED=y +# CONFIG_BT_BLUEDROID_ENABLED is not set +CONFIG_BT_NIMBLE_ENABLED=y +# CONFIG_BT_CONTROLLER_ONLY is not set +CONFIG_BT_CONTROLLER_ENABLED=y +# CONFIG_BT_CONTROLLER_DISABLED is not set + +# +# NimBLE Options +# +CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +# CONFIG_BT_NIMBLE_LOG_LEVEL_NONE is not set +# CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR is not set +# CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING is not set +CONFIG_BT_NIMBLE_LOG_LEVEL_INFO=y +# CONFIG_BT_NIMBLE_LOG_LEVEL_DEBUG is not set +CONFIG_BT_NIMBLE_LOG_LEVEL=1 +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3 +CONFIG_BT_NIMBLE_MAX_BONDS=3 +CONFIG_BT_NIMBLE_MAX_CCCDS=8 +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=0 +# CONFIG_BT_NIMBLE_PINNED_TO_CORE_0 is not set +CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y +CONFIG_BT_NIMBLE_PINNED_TO_CORE=1 +CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=y +CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y +CONFIG_BT_NIMBLE_ROLE_OBSERVER=y +CONFIG_BT_NIMBLE_GATT_CLIENT=y +CONFIG_BT_NIMBLE_GATT_SERVER=y +# CONFIG_BT_NIMBLE_NVS_PERSIST is not set +# CONFIG_BT_NIMBLE_SMP_ID_RESET is not set +CONFIG_BT_NIMBLE_SECURITY_ENABLE=y +CONFIG_BT_NIMBLE_SM_LEGACY=y +CONFIG_BT_NIMBLE_SM_SC=y +# CONFIG_BT_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_ENCRYPTION=y +CONFIG_BT_NIMBLE_SM_LVL=0 +CONFIG_BT_NIMBLE_SM_SC_ONLY=0 +CONFIG_BT_NIMBLE_PRINT_ERR_NAME=y +# CONFIG_BT_NIMBLE_DEBUG is not set +# CONFIG_BT_NIMBLE_DYNAMIC_SERVICE is not set +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=256 +CONFIG_BT_NIMBLE_ATT_MAX_PREP_ENTRIES=64 +CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE=0 + +# +# Memory Settings +# +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=256 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=24 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=320 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_COUNT=30 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_DISCARD_COUNT=8 +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=1 +# end of Memory Settings + +CONFIG_BT_NIMBLE_GATT_MAX_PROCS=4 +# CONFIG_BT_NIMBLE_HS_FLOW_CTRL is not set +CONFIG_BT_NIMBLE_RPA_TIMEOUT=900 +# CONFIG_BT_NIMBLE_MESH is not set +CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=y +CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000 +CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=y +CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3 +# CONFIG_BT_NIMBLE_HANDLE_REPEAT_PAIRING_DELETION is not set +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY=y +# CONFIG_BT_NIMBLE_EXT_ADV is not set +CONFIG_BT_NIMBLE_EXT_SCAN=y +CONFIG_BT_NIMBLE_ENABLE_PERIODIC_SYNC=y +CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=0 +# CONFIG_BT_NIMBLE_GATT_CACHING is not set +# CONFIG_BT_NIMBLE_INCL_SVC_DISCOVERY is not set +CONFIG_BT_NIMBLE_WHITELIST_SIZE=12 +# CONFIG_BT_NIMBLE_TEST_THROUGHPUT_TEST is not set +# CONFIG_BT_NIMBLE_BLUFI_ENABLE is not set +CONFIG_BT_NIMBLE_USE_ESP_TIMER=y +CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE=y +# CONFIG_BT_NIMBLE_BLE_GATT_BLOB_TRANSFER is not set + +# +# BLE Services +# +CONFIG_BT_NIMBLE_PROX_SERVICE=y +CONFIG_BT_NIMBLE_ANS_SERVICE=y +CONFIG_BT_NIMBLE_CTS_SERVICE=y +CONFIG_BT_NIMBLE_HTP_SERVICE=y +CONFIG_BT_NIMBLE_IPSS_SERVICE=y +CONFIG_BT_NIMBLE_TPS_SERVICE=y +CONFIG_BT_NIMBLE_IAS_SERVICE=y +CONFIG_BT_NIMBLE_LLS_SERVICE=y +CONFIG_BT_NIMBLE_SPS_SERVICE=y +CONFIG_BT_NIMBLE_HR_SERVICE=y +# CONFIG_BT_NIMBLE_HID_SERVICE is not set +CONFIG_BT_NIMBLE_BAS_SERVICE=y +# CONFIG_BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY is not set +CONFIG_BT_NIMBLE_DIS_SERVICE=y +# CONFIG_BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME is not set +# CONFIG_BT_NIMBLE_SVC_DIS_SERIAL_NUMBER is not set +# CONFIG_BT_NIMBLE_SVC_DIS_HARDWARE_REVISION is not set +# CONFIG_BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION is not set +# CONFIG_BT_NIMBLE_SVC_DIS_SOFTWARE_REVISION is not set +# CONFIG_BT_NIMBLE_SVC_DIS_SYSTEM_ID is not set +# CONFIG_BT_NIMBLE_SVC_DIS_PNP_ID is not set +# CONFIG_BT_NIMBLE_SVC_DIS_INCLUDED is not set +CONFIG_BT_NIMBLE_GAP_SERVICE=y + +# +# GAP Appearance write permissions +# +# CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE is not set +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM=0 +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC=0 +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN=0 +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR=0 +# end of GAP Appearance write permissions + +CONFIG_BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP=y +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_NOT_SUPP is not set +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_SUPP is not set +CONFIG_BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION=-1 + +# +# GAP device name write permissions +# +# CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE is not set +# end of GAP device name write permissions + +# +# Peripheral Preferred Connection Parameters (PPCP) settings +# +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL=0 +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL=0 +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY=0 +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO=0 +# end of Peripheral Preferred Connection Parameters (PPCP) settings + +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM=0 +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC=0 +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN=0 +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR=0 +# CONFIG_BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL is not set +# CONFIG_BT_NIMBLE_SVC_GAP_RPA_ONLY is not set +# end of BLE Services + +# CONFIG_BT_NIMBLE_VS_SUPPORT is not set +# CONFIG_BT_NIMBLE_ENC_ADV_DATA is not set +# CONFIG_BT_NIMBLE_HIGH_DUTY_ADV_ITVL is not set +# CONFIG_BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN is not set +# CONFIG_BT_NIMBLE_HOST_QUEUE_CONG_CHECK is not set +# CONFIG_BT_NIMBLE_GATTC_PROC_PREEMPTION_PROTECT is not set +# CONFIG_BT_NIMBLE_GATTC_AUTO_PAIR is not set + +# +# Host-controller Transport +# +CONFIG_UART_HW_FLOWCTRL_DISABLE=y +# CONFIG_UART_HW_FLOWCTRL_CTS_RTS is not set +CONFIG_BT_NIMBLE_HCI_UART_FLOW_CTRL=0 +CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=19 +CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport + +CONFIG_BT_NIMBLE_EATT_CHAN_NUM=0 +# CONFIG_BT_NIMBLE_SUBRATE is not set +# end of NimBLE Options + +# +# Controller Options +# +CONFIG_BT_CTRL_MODE_EFF=1 +CONFIG_BT_CTRL_BLE_MAX_ACT=6 +CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=6 +CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0 +CONFIG_BT_CTRL_PINNED_TO_CORE_0=y +# CONFIG_BT_CTRL_PINNED_TO_CORE_1 is not set +CONFIG_BT_CTRL_PINNED_TO_CORE=0 +CONFIG_BT_CTRL_HCI_MODE_VHCI=y +# CONFIG_BT_CTRL_HCI_MODE_UART_H4 is not set +CONFIG_BT_CTRL_HCI_TL=1 +CONFIG_BT_CTRL_ADV_DUP_FILT_MAX=30 +CONFIG_BT_BLE_CCA_MODE_NONE=y +# CONFIG_BT_BLE_CCA_MODE_HW is not set +# CONFIG_BT_BLE_CCA_MODE_SW is not set +CONFIG_BT_BLE_CCA_MODE=0 +CONFIG_BT_CTRL_HW_CCA_VAL=75 +CONFIG_BT_CTRL_HW_CCA_EFF=0 +CONFIG_BT_CTRL_CE_LENGTH_TYPE_ORIG=y +# CONFIG_BT_CTRL_CE_LENGTH_TYPE_CE is not set +# CONFIG_BT_CTRL_CE_LENGTH_TYPE_SD is not set +CONFIG_BT_CTRL_CE_LENGTH_TYPE_EFF=0 +CONFIG_BT_CTRL_TX_ANTENNA_INDEX_0=y +# CONFIG_BT_CTRL_TX_ANTENNA_INDEX_1 is not set +CONFIG_BT_CTRL_TX_ANTENNA_INDEX_EFF=0 +CONFIG_BT_CTRL_RX_ANTENNA_INDEX_0=y +# CONFIG_BT_CTRL_RX_ANTENNA_INDEX_1 is not set +CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0 +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N24 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N21 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N18 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N15 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N12 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N9 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N6 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N3 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 +CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 +CONFIG_BT_CTRL_BLE_SCAN_DUPL=y +CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DEVICE=y +# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA is not set +# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA_DEVICE is not set +CONFIG_BT_CTRL_SCAN_DUPL_TYPE=0 +CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE=100 +CONFIG_BT_CTRL_DUPL_SCAN_CACHE_REFRESH_PERIOD=0 +# CONFIG_BT_CTRL_BLE_MESH_SCAN_DUPL_EN is not set +# CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_DIS=y +CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EFF=0 + +# +# MODEM SLEEP Options +# +# CONFIG_BT_CTRL_MODEM_SLEEP is not set +# end of MODEM SLEEP Options + +CONFIG_BT_CTRL_SLEEP_MODE_EFF=0 +CONFIG_BT_CTRL_SLEEP_CLOCK_EFF=0 +CONFIG_BT_CTRL_HCI_TL_EFF=1 +# CONFIG_BT_CTRL_AGC_RECORRECT_EN is not set +# CONFIG_BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set +# CONFIG_BT_BLE_ADV_DATA_LENGTH_ZERO_AUX is not set +CONFIG_BT_CTRL_CHAN_ASS_EN=y +CONFIG_BT_CTRL_LE_PING_EN=y + +# +# BLE disconnects when Instant Passed (0x28) occurs +# +# CONFIG_BT_CTRL_BLE_LLCP_CONN_UPDATE is not set +# CONFIG_BT_CTRL_BLE_LLCP_CHAN_MAP_UPDATE is not set +# CONFIG_BT_CTRL_BLE_LLCP_PHY_UPDATE is not set +# end of BLE disconnects when Instant Passed (0x28) occurs + +# CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY is not set +CONFIG_BT_CTRL_DTM_ENABLE=y +CONFIG_BT_CTRL_BLE_MASTER=y +# CONFIG_BT_CTRL_BLE_TEST is not set +CONFIG_BT_CTRL_BLE_SCAN=y +CONFIG_BT_CTRL_BLE_SECURITY_ENABLE=y +CONFIG_BT_CTRL_BLE_ADV=y +# CONFIG_BT_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set + +# +# Controller debug log Options (Experimental) +# +# end of Controller debug log Options (Experimental) +# end of Controller Options # # Common Options # +CONFIG_BT_ALARM_MAX_NUM=50 # CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set # CONFIG_BT_BLE_LOG_UHCI_OUT_ENABLED is not set # end of Common Options + +# CONFIG_BT_HCI_LOG_DEBUG_EN is not set # end of Bluetooth +# CONFIG_BLE_MESH is not set + # # Console Library # @@ -789,7 +1069,8 @@ CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y # Wireless Coexistence # CONFIG_ESP_COEX_ENABLED=y -# CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE is not set +CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y +# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set # CONFIG_ESP_COEX_GPIO_DEBUG is not set # end of Wireless Coexistence @@ -2233,6 +2514,11 @@ CONFIG_WL_SECTOR_SIZE=4096 # CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +# CONFIG_WIFI_PROV_BLE_BONDING is not set +CONFIG_WIFI_PROV_BLE_SEC_CONN=y +# CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set +# CONFIG_WIFI_PROV_BLE_NOTIFY is not set +# CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV is not set CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager @@ -2275,8 +2561,48 @@ CONFIG_STACK_CHECK_NONE=y # CONFIG_ESP32_APPTRACE_DEST_TRAX is not set CONFIG_ESP32_APPTRACE_DEST_NONE=y CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y -# CONFIG_EXTERNAL_COEX_ENABLE is not set -# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_BLUEDROID_ENABLED is not set +CONFIG_NIMBLE_ENABLED=y +CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +CONFIG_NIMBLE_MAX_CONNECTIONS=3 +CONFIG_NIMBLE_MAX_BONDS=3 +CONFIG_NIMBLE_MAX_CCCDS=8 +CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0 +# CONFIG_NIMBLE_PINNED_TO_CORE_0 is not set +CONFIG_NIMBLE_PINNED_TO_CORE_1=y +CONFIG_NIMBLE_PINNED_TO_CORE=1 +CONFIG_NIMBLE_TASK_STACK_SIZE=4096 +CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096 +CONFIG_NIMBLE_ROLE_CENTRAL=y +CONFIG_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_NIMBLE_ROLE_BROADCASTER=y +CONFIG_NIMBLE_ROLE_OBSERVER=y +# CONFIG_NIMBLE_NVS_PERSIST is not set +CONFIG_NIMBLE_SM_LEGACY=y +CONFIG_NIMBLE_SM_SC=y +# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_BT_NIMBLE_SM_SC_LVL=0 +# CONFIG_NIMBLE_DEBUG is not set +CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_NIMBLE_ATT_PREFERRED_MTU=256 +CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0 +CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24 +CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255 +CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30 +CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8 +# CONFIG_NIMBLE_HS_FLOW_CTRL is not set +CONFIG_NIMBLE_RPA_TIMEOUT=900 +# CONFIG_NIMBLE_MESH is not set +CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y +# CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y +CONFIG_SW_COEXIST_ENABLE=y +CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y +CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y # CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set # CONFIG_GPTIMER_ISR_IRAM_SAFE is not set # CONFIG_MCPWM_ISR_IRAM_SAFE is not set diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..3875ab9 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,8 @@ +# BLE host transport (NimBLE) + WiFi/ESP-NOW coexistence +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y + +# Pin stacks to different cores (WiFi stays on core 0) +CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y