mirror of
https://github.com/tronbyt/server.git
synced 2026-08-31 06:57:10 +02:00
Fix/ws wait for displaying ack (#884)
Build and test / Lint & Quality Checks (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-darwin-arm64, arm64, darwin, macos-26) (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-linux-amd64, amd64, linux, ubuntu-24.04) (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-linux-arm64, arm64, linux, ubuntu-24.04-arm) (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-windows-amd64.exe, amd64, windows, windows-2025) (push) Has been cancelled
Create and publish a container image / build-and-push-image (push) Has been cancelled
Build and test / Create Release (push) Has been cancelled
Build and test / Lint & Quality Checks (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-darwin-arm64, arm64, darwin, macos-26) (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-linux-amd64, amd64, linux, ubuntu-24.04) (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-linux-arm64, arm64, linux, ubuntu-24.04-arm) (push) Has been cancelled
Build and test / build-and-test (tronbyt-server-windows-amd64.exe, amd64, windows, windows-2025) (push) Has been cancelled
Create and publish a container image / build-and-push-image (push) Has been cancelled
Build and test / Create Release (push) Has been cancelled
* Wait indefinitely for displaying ACK on WS v1+ devices. Remove the 30s fallback timeout so long-running WebP apps are not skipped while the device is still animating. Legacy firmware without protocol_version still uses dwell-based timing. Co-authored-by: Cursor <cursoragent@cursor.com> * Add 10-minute safety timeout when displaying ACK is missing. Prevents WS rotation from stalling forever on v1+ firmware while still allowing long WebP animations to complete. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -102,8 +102,9 @@ The device sends JSON messages to the server to acknowledge the state of images.
|
||||
|
||||
The server uses a sophisticated acknowledgment system to manage the flow of images.
|
||||
|
||||
* After sending an image, the server waits for an `Image Displaying` message from the device.
|
||||
* **Timeout for Old Firmware**: If the server doesn't receive an acknowledgment within a certain timeout period, it assumes the device is running older firmware that doesn't send acknowledgments. It then falls back to a simple time-based delay (`dwell_time`) between sending images.
|
||||
* After sending an image, the server waits for an `Image Displaying` message from the device before sending the next image.
|
||||
* **v1+ Firmware (protocol_version ≥ 1)**: The server waits for `displaying` before sending the next image. If no ACK arrives within 10 minutes (safety cap for dropped images or firmware bugs), the server logs a warning and advances rotation. Pushed preview images can interrupt the wait immediately.
|
||||
* **Legacy Firmware**: Devices without `protocol_version` do not send `displaying` ACKs. The server falls back to a dwell-time delay before sending the next image.
|
||||
* **Push Notifications**: The waiting process can be interrupted by server-side events, such as:
|
||||
* An ephemeral app being pushed to the device.
|
||||
* A change in the device's settings (e.g., brightness).
|
||||
|
||||
@@ -15,9 +15,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// minAckTimeoutSeconds is the minimum time to wait for device ACK.
|
||||
// This accounts for the previous app's dwell time, network latency, and processing overhead.
|
||||
minAckTimeoutSeconds = 30
|
||||
// maxDisplayingAckTimeoutSeconds is a safety cap for v1+ firmware when displaying
|
||||
// ACK never arrives (dropped image, firmware bug). Long enough for very long WebP
|
||||
// animations but prevents indefinite rotation stall.
|
||||
maxDisplayingAckTimeoutSeconds = 600
|
||||
)
|
||||
|
||||
type WSMessage struct {
|
||||
@@ -286,19 +287,19 @@ func (s *Server) wsWriteLoop(ctx context.Context, conn *websocket.Conn, initialD
|
||||
sendImmediate = false
|
||||
}
|
||||
|
||||
// 3. Wait for ACK or Timeout or Interrupt
|
||||
var timeoutSec int
|
||||
// 3. Wait for displaying ACK, safety/legacy timeout, or interrupt.
|
||||
// v1+ firmware sends displaying when the image is on screen (including long
|
||||
// animations). We wait for that ACK, with a long safety timeout if it never arrives.
|
||||
var timer *time.Timer
|
||||
var timerC <-chan time.Time
|
||||
if device.Info.ProtocolVersion != nil {
|
||||
// Device may delay ACK until previous app completes its dwell time.
|
||||
// Wait at least minAckTimeoutSeconds OR 2x the dwell time, whichever is greater.
|
||||
timeoutSec = max(dwell*2, minAckTimeoutSeconds)
|
||||
timer = time.NewTimer(time.Duration(maxDisplayingAckTimeoutSeconds) * time.Second)
|
||||
timerC = timer.C
|
||||
} else {
|
||||
// Old firmware: wait exactly dwell time
|
||||
timeoutSec = dwell
|
||||
timer = time.NewTimer(time.Duration(dwell) * time.Second)
|
||||
timerC = timer.C
|
||||
}
|
||||
|
||||
timer := time.NewTimer(time.Duration(timeoutSec) * time.Second)
|
||||
|
||||
interrupted := false
|
||||
waiting := true
|
||||
|
||||
@@ -325,7 +326,6 @@ func (s *Server) wsWriteLoop(ctx context.Context, conn *websocket.Conn, initialD
|
||||
slog.Debug("Received ACK for default or pushed image (no app context)", "device", device.ID)
|
||||
}
|
||||
}
|
||||
// If just Queued, we keep waiting for Displaying.
|
||||
case val := <-broadcastCh:
|
||||
// Update available (Reload device first)
|
||||
reloaded, err := gorm.G[data.Device](s.DB).Preload("Apps", nil).Where("id = ?", initialDevice.ID).First(ctx)
|
||||
@@ -379,15 +379,35 @@ func (s *Server) wsWriteLoop(ctx context.Context, conn *websocket.Conn, initialD
|
||||
lastSentBrightness = newBrightness
|
||||
}
|
||||
}
|
||||
case <-timer.C:
|
||||
// Timeout
|
||||
case <-timerC:
|
||||
if device.Info.ProtocolVersion != nil {
|
||||
appIname := ""
|
||||
if app != nil {
|
||||
appIname = app.Iname
|
||||
}
|
||||
slog.Warn(
|
||||
"Timed out waiting for displaying ACK, advancing rotation",
|
||||
"device", device.ID,
|
||||
"app", appIname,
|
||||
"timeout_secs", maxDisplayingAckTimeoutSeconds,
|
||||
)
|
||||
}
|
||||
waiting = false
|
||||
case <-stopCh:
|
||||
timer.Stop()
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
timer.Stop()
|
||||
if timer != nil {
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if interrupted {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user