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
15 KiB
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