Files
infoscreen-dev/HDMI_CEC_FLOW_DIAGRAM.md
RobbStarkAustria 6617c3d7b9 HDMI-CEC: auto-disable in dev mode + docs/tests synced
README: document dev-mode CEC auto-disable; add testing notes; set CEC_POWER_OFF_WAIT=5
Copilot instructions: add dev-mode CEC behavior and test script guidance
test-hdmi-cec.sh: respect ENV=development (skip option 5), explicit .env load, ASCII output, 30s wait
display_manager: remove emoji from logs to avoid Unicode errors
2025-11-12 17:09:11 +01:00

15 KiB

HDMI-CEC Integration Flow Diagram

System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     Infoscreen Client System                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────────┐         ┌──────────────┐                      │
│  │  MQTT Client │◄───────►│Display Manager│                      │
│  │ (simclient) │         │               │                      │
│  └──────┬───────┘         └───────┬──────┘                      │
│         │                         │                              │
│         │ Writes Events           │ Reads Events                 │
│         ▼                         ▼                              │
│  ┌────────────────────────────────────┐                         │
│  │     current_event.json             │                         │
│  └────────────────────────────────────┘                         │
│                                        │                          │
│         Display Manager Components     │                          │
│         ┌──────────────────────────────┴─────────────┐          │
│         │                                             │          │
│         ▼                                             ▼          │
│  ┌──────────────┐                           ┌────────────────┐  │
│  │ Event Handler│                           │HDMI CEC Control│  │
│  │              │───────────────────────────│                │  │
│  │ - Reads      │   1. Turn TV ON when     │ - turn_on()    │  │
│  │ - Validates  │      event starts        │ - turn_off()   │  │
│  │ - Schedules  │   2. Cancel turn-off     │ - cancel()     │  │
│  │              │      when active         │                │  │
│  └──────┬───────┘   3. Schedule turn-off  └────────┬───────┘  │
│         │              when ends                    │          │
│         │                                            │          │
│         ▼                                            ▼          │
│  ┌────────────────┐                         ┌────────────────┐ │
│  │Display Processes│                         │  cec-client   │ │
│  │- Impressive    │                         │   (binary)    │ │
│  │- Chromium      │                         └───────┬────────┘ │
│  │- VLC           │                                 │          │
│  └────────────────┘                                 │          │
│                                                      │          │
└──────────────────────────────────────────────────────┼──────────┘
                                                       │
                                              ┌────────▼────────┐
                                              │   HDMI-CEC Bus  │
                                              └────────┬────────┘
                                                       │
                                              ┌────────▼────────┐
                                              │   TV Display    │
                                              │                 │
                                              │ ON ◄─────► OFF  │
                                              └─────────────────┘

Event Lifecycle with CEC

1. Event Starts

MQTT Event Arrives
       │
       ├─► Write to current_event.json
       │
       ▼
Display Manager Detects New Event
       │
       ├─► Turn TV ON via CEC ──► echo "on 0" | cec-client -s -d 1
       │                                │
       ├─► Cancel any pending           │
       │   turn-off timer               ▼
       │                           TV Powers ON
       ▼
Start Display Process (Impressive/Chromium/VLC)

2. Event Active (Continuous)

Main Loop (every 5s)
       │
       ├─► Check if event still active
       │
       ├─► Process still running?
       │
       └─► Cancel turn-off timer ──► Keep TV ON
                 (prevents premature shutdown)

3. Event Ends

No Active Event Detected
       │
       ├─► Stop Display Process
       │
       ▼
Schedule TV Turn-Off (with delay)
       │
       ├─► threading.Timer(30s)
       │
       ▼
    [DELAY]
       │
       ├─► Can be cancelled if new event arrives
       │
       ▼
 Delay Expires
       │
       ▼
Turn TV OFF ──► echo "standby 0" | cec-client -s -d 1
       │
       ▼
TV Powers OFF (Standby)

4. Event Switching (No TV Off)

Event A Running
       │
       ▼
Event B Arrives
       │
       ├─► Stop Event A Display (turn_off_tv=False)
       │
       ├─► TV stays ON
       │
       ├─► Cancel any pending turn-off
       │
       ▼
Start Event B Display
       │
       └─► TV remains ON (seamless transition)

CEC Command Flow

Turn On Sequence

Python Code                    Shell Command                  CEC Bus
─────────────                  ──────────────                 ─────────

turn_on() called
    │
    ├─► Check if already ON ──► Skip if tv_state == True
    │
    ├─► Build command
    │
    ▼
subprocess.run()  ──────────►  echo "on 0" |       ────────►  CEC: Power On (0x04)
                               cec-client -s -d 1              Device 0 (TV)
    │                                  │
    ▼                                  ▼
Parse output                     STDOUT: "power status changed..."
    │
    ├─► Success indicators found?
    │
    ├─► Update tv_state = True
    │
    └─► Return True

Turn Off (Delayed) Sequence

Python Code                    Timer Thread                   Shell Command
─────────────                  ──────────────                 ──────────────

turn_off(delayed=True)
    │
    ├─► Create threading.Timer(30s, _turn_off_now)
    │
    ├─► timer.daemon = True
    │
    └─► timer.start()
            │
            │ [30 seconds pass]
            │
            ▼
       _turn_off_now()  ──────────►  subprocess.run()
            │                              │
            │                              ▼
            │                        echo "standby 0" | cec-client -s -d 1
            │                              │
            ▼                              ▼
       Update tv_state = False         TV enters standby

Cancel Turn-Off Sequence

Timer Active (counting down)
       │
       ▼
cancel_turn_off() called
       │
       ├─► timer.cancel()
       │
       └─► Timer stopped, TV stays ON

State Diagram

                                     ┌──────────────┐
                                     │  System Init │
                                     └──────┬───────┘
                                            │
                                            ▼
                                     ┌──────────────┐
                         ┌───────────│  Detect TV   │───────────┐
                         │           │    State     │           │
                         │           └──────────────┘           │
                         │                                      │
                  ┌──────▼─────┐                        ┌──────▼─────┐
                  │  TV is ON  │                        │ TV is OFF  │
                  │ (or unknown)│                       │ (standby)  │
                  └──────┬─────┘                        └──────┬─────┘
                         │                                      │
              ┌──────────┴──────────────────────────────────────┘
              │
              │    Event Arrives
              ▼
       ┌────────────┐
       │ Turn TV ON │────► CEC Command: "on 0"
       └──────┬─────┘
              │
              ▼
       ┌────────────┐
       │  TV is ON  │
       │ Event Active│◄────┐
       └──────┬─────┘      │
              │             │
              │ Event       │ Event Still
              │ Ends        │ Running
              │             │
              ▼             │
       ┌────────────┐       │
       │Start Timer │       │
       │   (30s)    │───────┘
       └──────┬─────┘   Cancel Timer
              │
              │ Timer
              │ Expires
              ▼
       ┌────────────┐
       │Turn TV OFF │────► CEC Command: "standby 0"
       └──────┬─────┘
              │
              ▼
       ┌────────────┐
       │ TV is OFF  │
       │  (standby) │
       └────────────┘
              │
              │ New Event
              └─────────► Turn TV ON (cycle repeats)

Configuration Impact

CEC_TURN_OFF_DELAY Values

Delay = 0 seconds (Immediate)
────────────────────────────────
Event End ──► TV OFF (no delay)

⚠️  May cause flicker if events are close together


Delay = 30 seconds (Default)
────────────────────────────────
Event End ──► [Wait 30s] ──► TV OFF

✓  Balanced approach
✓  Prevents flicker for events within 30s
✓  Still saves power


Delay = 120 seconds (Conservative)
────────────────────────────────
Event End ──► [Wait 2m] ──► TV OFF

✓  Very smooth for frequent events
⚠️  May waste power if events are sparse

Integration Points in Code

1. Initialization (DisplayManager.init)

self.cec = HDMICECController(
    enabled=CEC_ENABLED,
    device=CEC_DEVICE,
    turn_off_delay=CEC_TURN_OFF_DELAY
)

2. Signal Handler (Graceful Shutdown)

def _signal_handler(self, signum, frame):
    self.stop_current_display()
    self.cec.turn_off(delayed=True)  # TV off on shutdown
    sys.exit(0)

3. Event Processing (Main Loop)

# Turn on TV before starting display
self.cec.turn_on()

# During event
self.cec.cancel_turn_off()  # Keep TV on

# When stopping
self.stop_current_display(turn_off_tv=True)

4. Stop Display

def stop_current_display(self, turn_off_tv: bool = True):
    # ... terminate process ...
    if turn_off_tv:
        self.cec.turn_off(delayed=True)

Logging Flow

Display Manager Startup
       │
       ├──► [INFO] HDMI-CEC controller initialized (device: TV, turn_off_delay: 30s)
       │
       ├──► [INFO] TV detected as ON
       │
       └──► [INFO] Display Manager starting...

Event Arrives
       │
       ├──► [INFO] Starting display for event: presentation_slides.pdf
       │
       ├──► [INFO] Turning TV ON via HDMI-CEC...
       │
       ├──► [DEBUG] CEC command 'on 0' executed successfully
       │
       ├──► [INFO] TV turned ON successfully
       │
       └──► [INFO] Display started successfully

Event Ends
       │
       ├──► [INFO] No active events - stopping current display
       │
       ├──► [INFO] Scheduling TV turn-off in 30s...
       │
       └──► [INFO] Stopping current display

30 Seconds Later
       │
       ├──► [INFO] Turning TV OFF via HDMI-CEC...
       │
       ├──► [DEBUG] CEC command 'standby 0' executed successfully
       │
       └──► [INFO] TV turned OFF successfully

New Event Before Timeout
       │
       ├──► [DEBUG] Cancelled TV turn-off timer
       │
       └──► [INFO] Turning TV ON via HDMI-CEC...

Error Scenarios

1. cec-client Not Installed

HDMICECController.__init__()
       │
       ├──► _check_cec_available()
       │
       ├──► [WARNING] cec-client not found - HDMI-CEC control disabled
       │
       ├──► [INFO] Install with: sudo apt-get install cec-utils
       │
       └──► self.enabled = False
             (System continues without TV control)

2. TV Not Responding

turn_on() called
       │
       ├──► subprocess.run(...)
       │
       ├──► Exit code != 0
       │
       ├──► [WARNING] CEC command 'on 0' may have failed
       │
       └──► Return False
             (Logged but not critical)

3. CEC Command Timeout

_run_cec_command("on 0")
       │
       ├──► subprocess.run(..., timeout=5)
       │
       ├──► TimeoutExpired after 5s
       │
       ├──► [ERROR] CEC command 'on 0' timed out after 5s
       │
       └──► Return False

Summary

The HDMI-CEC integration provides:

  • Automatic TV control based on events
  • Configurable behavior via environment variables
  • Smart delay mechanism to prevent flicker
  • Graceful fallback if CEC unavailable
  • Comprehensive logging for troubleshooting
  • Minimal performance impact
  • Production-ready reliability