Add BLE RTT/stream tests and live tick median in the UI, fix concurrent WebSocket writes and late BATTERY replies, and skip 2M PHY updates that assert under ESP-NOW coexistence. Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type bleRttAPIRequest struct {
|
|
Count *int `json:"count"`
|
|
Warmup *int `json:"warmup"`
|
|
SettleS *float64 `json:"settle_s"`
|
|
}
|
|
|
|
type bleStreamAPIRequest struct {
|
|
ClientID *uint32 `json:"client_id"`
|
|
PeriodMs *uint32 `json:"period_ms"`
|
|
Count *int `json:"count"`
|
|
TolMs *float64 `json:"tol_ms"`
|
|
TimeoutS *float64 `json:"timeout_s"`
|
|
}
|
|
|
|
func mountBleTestAPI(mux *http.ServeMux, link *managedHost) {
|
|
mux.HandleFunc("/api/ble-rtt", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
serveBleRtt(w, r, link)
|
|
})
|
|
mux.HandleFunc("/api/ble-stream", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
serveBleStreamTest(w, r, link)
|
|
})
|
|
}
|
|
|
|
func requireBLEHost(w http.ResponseWriter, link *managedHost) bool {
|
|
if link.Transport() != hostTransportBLE || !link.IsConnected() {
|
|
writeJSON(w, http.StatusBadRequest, bleRttResult{
|
|
Error: "BLE host required (connect via BLE, DIP8=1)",
|
|
})
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func serveBleRtt(w http.ResponseWriter, r *http.Request, link *managedHost) {
|
|
if !requireBLEHost(w, link) {
|
|
return
|
|
}
|
|
var body bleRttAPIRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil && !errors.Is(err, io.EOF) {
|
|
writeJSON(w, http.StatusBadRequest, bleRttResult{Error: "invalid JSON"})
|
|
return
|
|
}
|
|
count, warmup := 50, 5
|
|
settle := 0.0
|
|
if body.Count != nil {
|
|
count = *body.Count
|
|
}
|
|
if body.Warmup != nil {
|
|
warmup = *body.Warmup
|
|
}
|
|
if body.SettleS != nil {
|
|
settle = *body.SettleS
|
|
}
|
|
|
|
if !link.beginPausePoll() {
|
|
writeJSON(w, http.StatusConflict, bleRttResult{Error: "host busy"})
|
|
return
|
|
}
|
|
defer link.endPausePoll()
|
|
|
|
out := measureBleRtt(link, count, warmup, time.Duration(settle*float64(time.Second)))
|
|
status := http.StatusOK
|
|
if !out.Success {
|
|
status = http.StatusBadRequest
|
|
}
|
|
writeJSON(w, status, out)
|
|
}
|
|
|
|
func serveBleStreamTest(w http.ResponseWriter, r *http.Request, link *managedHost) {
|
|
if !requireBLEHost(w, link) {
|
|
return
|
|
}
|
|
var body bleStreamAPIRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil && !errors.Is(err, io.EOF) {
|
|
writeJSON(w, http.StatusBadRequest, bleStreamResult{Error: "invalid JSON"})
|
|
return
|
|
}
|
|
count := 50
|
|
periodMs := uint32(16)
|
|
tolMs := 2.0
|
|
timeoutS := 15.0
|
|
var clientID uint32
|
|
if body.Count != nil {
|
|
count = *body.Count
|
|
}
|
|
if body.PeriodMs != nil {
|
|
periodMs = *body.PeriodMs
|
|
}
|
|
if body.TolMs != nil {
|
|
tolMs = *body.TolMs
|
|
}
|
|
if body.TimeoutS != nil {
|
|
timeoutS = *body.TimeoutS
|
|
}
|
|
if body.ClientID != nil {
|
|
clientID = *body.ClientID
|
|
}
|
|
|
|
if !link.beginPausePoll() {
|
|
writeJSON(w, http.StatusConflict, bleStreamResult{Error: "host busy"})
|
|
return
|
|
}
|
|
defer link.endPausePoll()
|
|
|
|
out := measureBleStream(link, clientID, periodMs, count, tolMs, timeoutS)
|
|
status := http.StatusOK
|
|
if !out.Success {
|
|
status = http.StatusBadRequest
|
|
}
|
|
writeJSON(w, status, out)
|
|
}
|