Add dynamic UART/BLE host connect for serve, dashboard, and WS.
Serve starts without a required port; REST/WS/UI can scan and switch transports, with BLE live ticks and UART-only OTA. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+71
-14
@@ -66,8 +66,12 @@ type ClientView struct {
|
||||
}
|
||||
|
||||
type DashboardState struct {
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
SerialPort string `json:"serial_port"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
SerialPort string `json:"serial_port"`
|
||||
/** Alias of host_endpoint for older UI. */
|
||||
HostEndpoint string `json:"host_endpoint"`
|
||||
HostTransport string `json:"host_transport"`
|
||||
HostConnected bool `json:"host_connected"`
|
||||
UARTConnected bool `json:"uart_connected"`
|
||||
SerialOK bool `json:"serial_ok"`
|
||||
SerialError string `json:"serial_error,omitempty"`
|
||||
@@ -530,20 +534,35 @@ func (h *wsHub) broadcastRaw(v any) {
|
||||
}
|
||||
|
||||
func pollDashboard(link *managedSerial, portName string, last *DashboardState, streamCtl *accelStreamCtl, tapCtl *tapNotifyCtl) DashboardState {
|
||||
ep := link.Endpoint()
|
||||
if ep == "" {
|
||||
ep = portName
|
||||
}
|
||||
tr := link.Transport()
|
||||
st := DashboardState{
|
||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
SerialPort: portName,
|
||||
Clients: []ClientView{},
|
||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
SerialPort: ep,
|
||||
HostEndpoint: ep,
|
||||
HostTransport: tr,
|
||||
Clients: []ClientView{},
|
||||
}
|
||||
if !link.IsConnected() && tr == hostTransportNone {
|
||||
st.SerialError = "not connected — use UART or BLE connect"
|
||||
return st
|
||||
}
|
||||
|
||||
ver, err := link.getVersionPoll()
|
||||
if errors.Is(err, errUARTBusy) {
|
||||
return pausedPollState(portName, last)
|
||||
return pausedPollState(ep, last)
|
||||
}
|
||||
if err != nil {
|
||||
return disconnectedState(portName, err)
|
||||
return disconnectedState(ep, err)
|
||||
}
|
||||
st.UARTConnected = true
|
||||
st.HostConnected = true
|
||||
st.HostTransport = link.Transport()
|
||||
st.HostEndpoint = link.Endpoint()
|
||||
st.SerialPort = st.HostEndpoint
|
||||
st.SerialOK = true
|
||||
st.Master = MasterView{
|
||||
Version: ver.GetVersion(),
|
||||
@@ -565,7 +584,8 @@ func pollDashboard(link *managedSerial, portName string, last *DashboardState, s
|
||||
}
|
||||
st.SerialOK = false
|
||||
st.SerialError = err.Error()
|
||||
st.UARTConnected = link.IsConnected()
|
||||
st.HostConnected = link.IsConnected()
|
||||
st.UARTConnected = st.HostConnected
|
||||
return st
|
||||
}
|
||||
|
||||
@@ -677,6 +697,10 @@ func runCacheStatusDashboardPoller(link *managedSerial, hub *wsHub, interval tim
|
||||
if !hub.liveStreamEnabled() {
|
||||
continue
|
||||
}
|
||||
// BLE live path prefers BLE_STREAM_TICK pushes.
|
||||
if link.Transport() == hostTransportBLE {
|
||||
continue
|
||||
}
|
||||
cache, err := link.readCacheStatusPoll()
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -687,6 +711,36 @@ func runCacheStatusDashboardPoller(link *managedSerial, hub *wsHub, interval tim
|
||||
}
|
||||
}
|
||||
|
||||
// runBLEStreamDashboardPoller feeds live accel/tap from BLE_STREAM_TICK when live_stream is on.
|
||||
func runBLEStreamDashboardPoller(link *managedSerial, hub *wsHub, streamCtl *accelStreamCtl, stop <-chan struct{}) {
|
||||
_ = streamCtl
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
if link.Transport() != hostTransportBLE || !hub.liveStreamEnabled() {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
continue
|
||||
}
|
||||
payload, err := link.NextStreamTick(200 * time.Millisecond)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
cache, err := cacheStatusFromBLEStreamPayload(payload)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hub.mergeAccel(accelSamplesFromCacheStatus(cache))
|
||||
hub.mergeTap(tapEventsFromCacheStatus(cache))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *wsHub) clientCount() int {
|
||||
h.mu.RLock()
|
||||
n := len(h.clients)
|
||||
@@ -695,11 +749,14 @@ func (h *wsHub) clientCount() int {
|
||||
}
|
||||
|
||||
func pausedPollState(portName string, last *DashboardState) DashboardState {
|
||||
if last != nil && last.UARTConnected {
|
||||
if last != nil && (last.HostConnected || last.UARTConnected) {
|
||||
st := *last
|
||||
st.UpdatedAt = time.Now().Format(time.RFC3339)
|
||||
st.SerialPort = portName
|
||||
st.HostEndpoint = portName
|
||||
st.SerialOK = true
|
||||
st.HostConnected = true
|
||||
st.UARTConnected = true
|
||||
st.SerialError = "Live-Polling pausiert (OTA läuft)"
|
||||
return st
|
||||
}
|
||||
@@ -774,20 +831,20 @@ func runPoller(link *managedSerial, portName string, hub *wsHub, streamCtl *acce
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
uartUp := false
|
||||
hostUp := false
|
||||
var lastGood DashboardState
|
||||
publish := func() {
|
||||
st := pollDashboard(link, portName, &lastGood, streamCtl, tapCtl)
|
||||
hub.setState(st)
|
||||
if st.UARTConnected && st.SerialOK {
|
||||
if st.HostConnected && st.SerialOK {
|
||||
hub.mu.RLock()
|
||||
lastGood = hub.state
|
||||
hub.mu.RUnlock()
|
||||
}
|
||||
if st.UARTConnected && !uartUp {
|
||||
log.Printf("UART %s connected", portName)
|
||||
if st.HostConnected && !hostUp {
|
||||
log.Printf("host %s connected (%s)", st.HostEndpoint, st.HostTransport)
|
||||
}
|
||||
uartUp = st.UARTConnected
|
||||
hostUp = st.HostConnected
|
||||
}
|
||||
|
||||
publish()
|
||||
|
||||
Reference in New Issue
Block a user