Rocket Telemetry Project Docs

ADR-005: Per-Target Parameter Binding & Fallback Resolution

Decision record for per-target UI parameter binding, fallback hierarchy, and observability.

Metadata


Problem / Context

The architecture supports per-target UI binding (System-APIs-Contracts#per-target-ui-binding-model) and System-ControlDesign mention "frame configuration" and "fallback scopes." However, several binding resolution questions remain:

  1. Fallback hierarchy: If target-specific binding doesn't exist, what's the cascade? (frame-type → antenna → global?)
  2. Missing bindings: If no binding exists at any scope level, does UI show blank field, or use a sensible default?
  3. Operator override: Can operator manually override binding at runtime? (e.g., force a target to use a different color?)
  4. Partial bindings: If only some UI properties are bound (e.g., color but not unit?), how are gaps filled?
  5. Binding version: If binding schema changes, do old targets use old or new binding?
  6. Multi-target conflicts: If two targets both want to control the same antenna, who wins?

Current State

Why This Matters

  • Operator experience: Intuitive fallback prevents operator confusion ("why is target 2 showing wrong color?")
  • Flexibility: Fallback allows operator to add targets mid-mission without UI reconfiguration
  • Consistency: Clear rules ensure all operators see same UI across targets
  • Backward compatibility: Old missions can use new software (binding evolution)

Deferred Decision Options

Option A: Strict Hierarchy with Smart Defaults (Flexible)

Approach: Define a strict fallback hierarchy. At each level, provide sensible defaults for missing properties.

Hierarchy (top = highest priority):

  1. Target-specific binding (e.g., "target-1-telemetry")
  2. Frame-type binding (e.g., "ccsds-20-byte-header")
  3. Antenna binding (e.g., "phased-array-1")
  4. Global binding (e.g., "default-ui-binding")

Smart defaults (if all levels missing):

  • Color: Assign from pastel palette (target 1 → light blue, target 2 → light green, etc.)
  • Unit: Auto-detect from field name + value magnitude (e.g., "altitude" → meters)
  • Range: Auto-scale from min/max observed in recent data
  • Label: Use field name from schema (e.g., "altitude_m")

Pros:

  • Predictable: operator knows exactly which rule applies
  • Backward compatible: old targets can use global binding
  • Reduces manual config: smart defaults cover 80% of use cases
  • Easy to debug: log which binding level was used for each field

Cons:

  • Operator must understand hierarchy to troubleshoot
  • Smart defaults can be wrong (e.g., "target1_id" wrongly auto-colored as continuous field)
  • Hierarchy adds code complexity (4 database lookups per field)

Implementation:

def resolve_binding(target_id, frame_type, antenna_id, field_name):
    """Resolve UI binding using strict hierarchy."""
    # Level 1: Target-specific binding
    binding = db.query_binding(scope="target", id=target_id, field=field_name)
    if binding:
        return binding
    
    # Level 2: Frame-type binding
    binding = db.query_binding(scope="frame_type", id=frame_type, field=field_name)
    if binding:
        return binding
    
    # Level 3: Antenna binding
    binding = db.query_binding(scope="antenna", id=antenna_id, field=field_name)
    if binding:
        return binding

On this page