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
This commit is contained in:
421
HDMI_CEC_FLOW_DIAGRAM.md
Normal file
421
HDMI_CEC_FLOW_DIAGRAM.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# 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__)
|
||||
```python
|
||||
self.cec = HDMICECController(
|
||||
enabled=CEC_ENABLED,
|
||||
device=CEC_DEVICE,
|
||||
turn_off_delay=CEC_TURN_OFF_DELAY
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Signal Handler (Graceful Shutdown)
|
||||
```python
|
||||
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)
|
||||
```python
|
||||
# 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
|
||||
```python
|
||||
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
|
||||
Reference in New Issue
Block a user