feat(sense): complete T-006 five-stream integration
This commit is contained in:
@@ -25,6 +25,25 @@ type ReconcileCandidate struct {
|
||||
NextAttempt *time.Time
|
||||
}
|
||||
|
||||
type DeviceConvergence struct {
|
||||
ID string `json:"id"`
|
||||
PathName string `json:"path_name"`
|
||||
DesiredState device.DesiredState `json:"desired_state"`
|
||||
ActualState device.ActualState `json:"actual_state"`
|
||||
Generation int64 `json:"generation"`
|
||||
ObservedGeneration int64 `json:"observed_generation"`
|
||||
FailureCount int `json:"failure_count"`
|
||||
NextAttemptAt *time.Time `json:"next_attempt_at,omitempty"`
|
||||
LastErrorCode string `json:"last_error_code,omitempty"`
|
||||
Converged bool `json:"converged"`
|
||||
}
|
||||
|
||||
type ConvergenceSnapshot struct {
|
||||
Total int `json:"total"`
|
||||
Unconverged int `json:"unconverged"`
|
||||
Devices []DeviceConvergence `json:"devices"`
|
||||
}
|
||||
|
||||
type SQLite struct {
|
||||
db *sql.DB
|
||||
}
|
||||
@@ -521,6 +540,76 @@ func (s *SQLite) UpdateActualState(ctx context.Context, id string, state device.
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestReconcile invalidates the observed generation without changing the
|
||||
// desired state or retry backoff. Runtime probes use it when MediaMTX loses a
|
||||
// configured path, including after a MediaMTX process restart.
|
||||
func (s *SQLite) RequestReconcile(ctx context.Context, id string, now time.Time) error {
|
||||
result, err := s.db.ExecContext(ctx, `
|
||||
UPDATE sense_reconcile_state
|
||||
SET observed_generation = 0, updated_at = ?
|
||||
WHERE device_id = ?`, formatTime(now), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request device reconciliation: %w", err)
|
||||
}
|
||||
if affected, _ := result.RowsAffected(); affected != 1 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvergenceSnapshot returns only identifiers, state and counters. Endpoint
|
||||
// and credential references are deliberately excluded from diagnostics.
|
||||
func (s *SQLite) ConvergenceSnapshot(ctx context.Context) (ConvergenceSnapshot, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT d.id, d.path_name, d.desired_state, d.actual_state, d.generation,
|
||||
r.observed_generation, r.failure_count, r.next_attempt_at, r.last_error_code
|
||||
FROM sense_devices d
|
||||
JOIN sense_reconcile_state r ON r.device_id = d.id
|
||||
WHERE d.desired_state = 'enabled'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM sense_device_capabilities c
|
||||
WHERE c.device_id = d.id AND c.capability = 'video_capture'
|
||||
)
|
||||
ORDER BY d.id`)
|
||||
if err != nil {
|
||||
return ConvergenceSnapshot{}, fmt.Errorf("query convergence snapshot: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
snapshot := ConvergenceSnapshot{Devices: make([]DeviceConvergence, 0)}
|
||||
for rows.Next() {
|
||||
var value DeviceConvergence
|
||||
var nextAttempt, lastError sql.NullString
|
||||
if err := rows.Scan(
|
||||
&value.ID, &value.PathName, &value.DesiredState, &value.ActualState,
|
||||
&value.Generation, &value.ObservedGeneration, &value.FailureCount,
|
||||
&nextAttempt, &lastError,
|
||||
); err != nil {
|
||||
return ConvergenceSnapshot{}, fmt.Errorf("scan convergence snapshot: %w", err)
|
||||
}
|
||||
if nextAttempt.Valid {
|
||||
parsed, parseErr := parseTime(nextAttempt.String)
|
||||
if parseErr != nil {
|
||||
return ConvergenceSnapshot{}, parseErr
|
||||
}
|
||||
value.NextAttemptAt = &parsed
|
||||
}
|
||||
if lastError.Valid {
|
||||
value.LastErrorCode = lastError.String
|
||||
}
|
||||
value.Converged = value.ObservedGeneration == value.Generation &&
|
||||
value.FailureCount == 0 && value.ActualState == device.ActualOnline
|
||||
if !value.Converged {
|
||||
snapshot.Unconverged++
|
||||
}
|
||||
snapshot.Devices = append(snapshot.Devices, value)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return ConvergenceSnapshot{}, fmt.Errorf("iterate convergence snapshot: %w", err)
|
||||
}
|
||||
snapshot.Total = len(snapshot.Devices)
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
const deviceColumns = `d.id, d.tenant_id, d.site_id, d.serial_number, d.name, d.modality,
|
||||
d.desired_state, d.actual_state, d.endpoint_ref, d.credential_ref,
|
||||
d.path_name, d.generation, d.created_at, d.updated_at`
|
||||
|
||||
Reference in New Issue
Block a user