Python bindings for libcec to control CEC-compliant HDMI devices from Python scripts
npx @tessl/cli install tessl/pypi-cec@0.2.00
# CEC (Consumer Electronics Control)
1
2
Python bindings for libcec, enabling control of CEC-compliant HDMI devices (TVs, receivers, etc.) from Python scripts. The library provides a comprehensive API for device discovery, power management, volume control, input switching, and event-driven programming through callbacks.
3
4
## Package Information
5
6
- **Package Name**: cec
7
- **Language**: Python (C++ extension)
8
- **Installation**: `pip install cec`
9
- **Hardware Requirements**: CEC-capable hardware (Pulse-Eight USB-CEC adapter, Raspberry Pi, etc.)
10
11
## Core Imports
12
13
```python
14
import cec
15
```
16
17
## Basic Usage
18
19
```python
20
import cec
21
22
# Initialize CEC library with default adapter
23
cec.init()
24
25
# Create a device object for the TV
26
tv = cec.Device(cec.CECDEVICE_TV)
27
28
# Control the TV
29
tv.power_on()
30
print("TV is on:", tv.is_on())
31
32
# Volume control
33
cec.volume_up()
34
cec.volume_down()
35
cec.toggle_mute()
36
37
# Clean shutdown (when supported)
38
# cec.close() # Not implemented in current version
39
```
40
41
## Capabilities
42
43
### Adapter Management
44
45
Initialize and manage CEC adapters for communication with the CEC bus.
46
47
```python { .api }
48
def list_adapters():
49
"""
50
List available CEC adapters on the system.
51
52
Returns:
53
list: Available adapter names/paths
54
"""
55
56
def init():
57
"""
58
Initialize CEC library with the default adapter.
59
60
Returns:
61
bool: True if initialization successful
62
63
Raises:
64
RuntimeError: If initialization fails
65
"""
66
67
def init(adapter):
68
"""
69
Initialize CEC library with a specific adapter.
70
71
Args:
72
adapter (str): Adapter name/path from list_adapters()
73
74
Returns:
75
bool: True if initialization successful
76
77
Raises:
78
RuntimeError: If initialization fails
79
"""
80
```
81
82
### Device Discovery
83
84
Discover and enumerate CEC devices on the bus.
85
86
```python { .api }
87
def list_devices():
88
"""
89
List all discovered CEC devices on the bus.
90
91
Returns:
92
dict: Dictionary mapping device logical addresses (int) to Device objects
93
"""
94
```
95
96
### Event System
97
98
Register and manage event callbacks for CEC events like key presses, commands, and state changes.
99
100
```python { .api }
101
def add_callback(handler, events):
102
"""
103
Add an event callback handler.
104
105
Args:
106
handler (callable): Function to handle events, signature varies by event type
107
events (int): Bitmask of event types to listen for
108
109
Returns:
110
bool: True if callback was registered successfully
111
"""
112
113
def remove_callback(handler, events):
114
"""
115
Remove an event callback handler.
116
117
Args:
118
handler (callable): Previously registered callback function
119
events (int): Bitmask of event types to stop listening for
120
121
Returns:
122
bool: True if callback was removed successfully
123
"""
124
```
125
126
### Raw Communication
127
128
Send raw CEC commands directly to devices on the bus.
129
130
```python { .api }
131
def transmit(destination, opcode, parameters=None, initiator=None):
132
"""
133
Transmit a raw CEC command to a specific device.
134
135
Args:
136
destination (int): Target device logical address (0-15)
137
opcode (int): CEC opcode for the command
138
parameters (bytes, optional): Command parameters as byte string
139
initiator (int, optional): Source logical address (0-15). If not specified, uses primary adapter address
140
141
Returns:
142
bool: True if transmission successful
143
"""
144
```
145
146
### Active Source Management
147
148
Manage which device is the active source on the CEC bus.
149
150
```python { .api }
151
def is_active_source(addr):
152
"""
153
Check if device at the given address is the active source.
154
155
Args:
156
addr (int): Device logical address
157
158
Returns:
159
bool: True if device is active source
160
"""
161
162
def set_active_source():
163
"""
164
Set the default device as the active source.
165
166
Returns:
167
bool: True if successful
168
"""
169
170
def set_active_source(device_type):
171
"""
172
Set a specific device type as the active source.
173
174
Args:
175
device_type (int): CEC device type constant
176
177
Returns:
178
bool: True if successful
179
"""
180
```
181
182
### Volume Control
183
184
Control audio volume and mute state through CEC.
185
186
```python { .api }
187
def volume_up():
188
"""
189
Increase the volume via CEC.
190
191
Returns:
192
bool: True if command sent successfully
193
"""
194
195
def volume_down():
196
"""
197
Decrease the volume via CEC.
198
199
Returns:
200
bool: True if command sent successfully
201
"""
202
203
def toggle_mute():
204
"""
205
Toggle mute state via CEC.
206
Available in libcec 2.0+ only.
207
208
Returns:
209
bool: True if command sent successfully
210
"""
211
```
212
213
### Physical Address Management
214
215
Configure HDMI physical addresses and stream paths.
216
217
```python { .api }
218
def set_stream_path(path):
219
"""
220
Set the HDMI stream path.
221
222
Args:
223
path (int): Physical address as integer
224
225
Returns:
226
bool: True if successful
227
"""
228
229
def set_physical_addr(addr):
230
"""
231
Set the HDMI physical address.
232
233
Args:
234
addr (int): Physical address as integer
235
236
Returns:
237
bool: True if successful
238
"""
239
```
240
241
### Configuration Persistence
242
243
Manage CEC configuration persistence to adapter hardware.
244
245
```python { .api }
246
def can_persist_config():
247
"""
248
Check if the current adapter supports configuration persistence.
249
250
Returns:
251
bool: True if adapter can persist configuration
252
"""
253
254
def persist_config():
255
"""
256
Persist the current CEC configuration to the adapter.
257
258
Returns:
259
bool: True if configuration was persisted successfully
260
"""
261
262
def set_port(device, port):
263
"""
264
Set upstream HDMI port for a device.
265
266
Args:
267
device (int): Device logical address
268
port (int): HDMI port number
269
270
Returns:
271
bool: True if successful
272
"""
273
```
274
275
### Device Objects
276
277
The Device class represents individual CEC devices and provides methods for device-specific control.
278
279
```python { .api }
280
class Device:
281
"""
282
Represents a CEC device on the bus.
283
"""
284
285
def __init__(self, id):
286
"""
287
Create a Device object for the given logical address.
288
289
Args:
290
id (int): Logical address of the device (0-15)
291
"""
292
293
# Read-only properties
294
@property
295
def address(self):
296
"""
297
Logical address of the device.
298
299
Returns:
300
int: Device logical address (0-15)
301
"""
302
303
@property
304
def physical_address(self):
305
"""
306
Physical address of the device in HDMI topology.
307
308
Returns:
309
int: Physical address as integer
310
"""
311
312
@property
313
def vendor(self):
314
"""
315
Vendor ID of the device.
316
317
Returns:
318
int: Vendor identification number
319
"""
320
321
@property
322
def osd_string(self):
323
"""
324
On-Screen Display name of the device.
325
326
Returns:
327
str: Device OSD name
328
"""
329
330
@property
331
def cec_version(self):
332
"""
333
CEC version supported by the device.
334
335
Returns:
336
int: CEC version number
337
"""
338
339
@property
340
def language(self):
341
"""
342
Menu language of the device.
343
344
Returns:
345
str: ISO language code
346
"""
347
348
# Methods
349
def is_on(self):
350
"""
351
Get the power status of the device.
352
353
Returns:
354
bool: True if device is powered on
355
"""
356
357
def power_on(self):
358
"""
359
Power on the device.
360
361
Returns:
362
bool: True if command sent successfully
363
"""
364
365
def standby(self):
366
"""
367
Put the device into standby mode.
368
369
Returns:
370
bool: True if command sent successfully
371
"""
372
373
def is_active(self):
374
"""
375
Check if this device is the active source.
376
377
Returns:
378
bool: True if device is active source
379
"""
380
381
def set_av_input(self, input):
382
"""
383
Select AV input on the device.
384
385
Args:
386
input (int): Input number/identifier
387
388
Returns:
389
bool: True if command sent successfully
390
"""
391
392
def set_audio_input(self, input):
393
"""
394
Select audio input on the device.
395
396
Args:
397
input (int): Audio input number/identifier
398
399
Returns:
400
bool: True if command sent successfully
401
"""
402
403
def transmit(self, opcode, parameters):
404
"""
405
Transmit a raw CEC command to this device.
406
407
Args:
408
opcode (int): CEC opcode for the command
409
parameters (bytes): Command parameters as byte string
410
411
Returns:
412
bool: True if transmission successful
413
"""
414
```
415
416
## Constants
417
418
### Event Types
419
420
Event type constants for use with callback registration:
421
422
```python { .api }
423
EVENT_LOG = 1 # Log message events
424
EVENT_KEYPRESS = 2 # Key press events
425
EVENT_COMMAND = 4 # CEC command events
426
EVENT_CONFIG_CHANGE = 8 # Configuration change events (not implemented)
427
EVENT_ALERT = 16 # Alert events
428
EVENT_MENU_CHANGED = 32 # Menu state change events
429
EVENT_ACTIVATED = 64 # Device activation events
430
EVENT_ALL = 127 # All event types combined
431
```
432
433
### Alert Types
434
435
Alert type constants for EVENT_ALERT callbacks:
436
437
```python { .api }
438
CEC_ALERT_SERVICE_DEVICE = 1 # Service device alert
439
CEC_ALERT_CONNECTION_LOST = 2 # Connection lost alert
440
CEC_ALERT_PERMISSION_ERROR = 3 # Permission error alert
441
CEC_ALERT_PORT_BUSY = 4 # Port busy alert
442
CEC_ALERT_PHYSICAL_ADDRESS_ERROR = 5 # Physical address error alert
443
CEC_ALERT_TV_POLL_FAILED = 6 # TV polling failed alert
444
```
445
446
### Menu States
447
448
Menu state constants for EVENT_MENU_CHANGED callbacks:
449
450
```python { .api }
451
CEC_MENU_STATE_ACTIVATED = 0 # Menu activated state
452
CEC_MENU_STATE_DEACTIVATED = 1 # Menu deactivated state
453
```
454
455
### Device Types
456
457
Device type constants for device classification:
458
459
```python { .api }
460
CEC_DEVICE_TYPE_TV = 0 # TV device type
461
CEC_DEVICE_TYPE_RECORDING_DEVICE = 1 # Recording device type
462
CEC_DEVICE_TYPE_RESERVED = 2 # Reserved device type
463
CEC_DEVICE_TYPE_TUNER = 3 # Tuner device type
464
CEC_DEVICE_TYPE_PLAYBACK_DEVICE = 4 # Playback device type
465
CEC_DEVICE_TYPE_AUDIO_SYSTEM = 5 # Audio system device type
466
```
467
468
### Logical Addresses
469
470
Logical address constants for device identification:
471
472
```python { .api }
473
CECDEVICE_UNKNOWN = -1 # Unknown device address
474
CECDEVICE_TV = 0 # TV logical address
475
CECDEVICE_RECORDINGDEVICE1 = 1 # Recording device 1 address
476
CECDEVICE_RECORDINGDEVICE2 = 2 # Recording device 2 address
477
CECDEVICE_TUNER1 = 3 # Tuner 1 address
478
CECDEVICE_PLAYBACKDEVICE1 = 4 # Playback device 1 address
479
CECDEVICE_AUDIOSYSTEM = 5 # Audio system address
480
CECDEVICE_TUNER2 = 6 # Tuner 2 address
481
CECDEVICE_TUNER3 = 7 # Tuner 3 address
482
CECDEVICE_PLAYBACKDEVICE2 = 8 # Playback device 2 address
483
CECDEVICE_RECORDINGDEVICE3 = 9 # Recording device 3 address
484
CECDEVICE_TUNER4 = 10 # Tuner 4 address
485
CECDEVICE_PLAYBACKDEVICE3 = 11 # Playback device 3 address
486
CECDEVICE_RESERVED1 = 12 # Reserved address 1
487
CECDEVICE_RESERVED2 = 13 # Reserved address 2
488
CECDEVICE_FREEUSE = 14 # Free use address
489
CECDEVICE_UNREGISTERED = 15 # Unregistered device address
490
CECDEVICE_BROADCAST = 15 # Broadcast address
491
```
492
493
### CEC Opcodes
494
495
CEC command opcodes for raw communication (selection of commonly used opcodes):
496
497
```python { .api }
498
# Power Management
499
CEC_OPCODE_STANDBY = 0x36 # Put device in standby
500
CEC_OPCODE_IMAGE_VIEW_ON = 0x04 # Turn on and show image
501
CEC_OPCODE_TEXT_VIEW_ON = 0x0D # Turn on and show text
502
CEC_OPCODE_GIVE_DEVICE_POWER_STATUS = 0x8F # Request power status
503
CEC_OPCODE_REPORT_POWER_STATUS = 0x90 # Report power status
504
505
# Active Source Management
506
CEC_OPCODE_ACTIVE_SOURCE = 0x82 # Set active source
507
CEC_OPCODE_INACTIVE_SOURCE = 0x9D # Set inactive source
508
CEC_OPCODE_REQUEST_ACTIVE_SOURCE = 0x85 # Request active source
509
CEC_OPCODE_SET_STREAM_PATH = 0x86 # Set stream path
510
CEC_OPCODE_ROUTING_CHANGE = 0x80 # Routing change
511
CEC_OPCODE_ROUTING_INFORMATION = 0x81 # Routing information
512
513
# Device Information
514
CEC_OPCODE_GIVE_PHYSICAL_ADDRESS = 0x83 # Request physical address
515
CEC_OPCODE_REPORT_PHYSICAL_ADDRESS = 0x84 # Report physical address
516
CEC_OPCODE_GIVE_OSD_NAME = 0x46 # Request OSD name
517
CEC_OPCODE_SET_OSD_NAME = 0x47 # Set OSD name
518
CEC_OPCODE_GIVE_DEVICE_VENDOR_ID = 0x8C # Request vendor ID
519
CEC_OPCODE_DEVICE_VENDOR_ID = 0x87 # Report vendor ID
520
CEC_OPCODE_GET_CEC_VERSION = 0x9F # Request CEC version
521
CEC_OPCODE_CEC_VERSION = 0x9E # Report CEC version
522
523
# Audio System
524
CEC_OPCODE_GIVE_AUDIO_STATUS = 0x71 # Request audio status
525
CEC_OPCODE_REPORT_AUDIO_STATUS = 0x7A # Report audio status
526
CEC_OPCODE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D # Request system audio mode
527
CEC_OPCODE_SYSTEM_AUDIO_MODE_STATUS = 0x7E # Report system audio mode
528
CEC_OPCODE_SET_SYSTEM_AUDIO_MODE = 0x72 # Set system audio mode
529
CEC_OPCODE_SYSTEM_AUDIO_MODE_REQUEST = 0x70 # Request system audio mode
530
531
# User Control
532
CEC_OPCODE_USER_CONTROL_PRESSED = 0x44 # User pressed button
533
CEC_OPCODE_USER_CONTROL_RELEASE = 0x45 # User released button
534
535
# Menu Control
536
CEC_OPCODE_MENU_REQUEST = 0x8D # Menu request
537
CEC_OPCODE_MENU_STATUS = 0x8E # Menu status
538
539
# Recording/Timer Control
540
CEC_OPCODE_RECORD_OFF = 0x0B # Stop recording
541
CEC_OPCODE_RECORD_ON = 0x09 # Start recording
542
CEC_OPCODE_RECORD_STATUS = 0x0A # Report recording status
543
CEC_OPCODE_RECORD_TV_SCREEN = 0x0F # Record TV screen
544
CEC_OPCODE_CLEAR_ANALOGUE_TIMER = 0x33 # Clear analogue timer
545
CEC_OPCODE_CLEAR_DIGITAL_TIMER = 0x99 # Clear digital timer
546
CEC_OPCODE_CLEAR_EXTERNAL_TIMER = 0xA1 # Clear external timer
547
CEC_OPCODE_SET_ANALOGUE_TIMER = 0x34 # Set analogue timer
548
CEC_OPCODE_SET_DIGITAL_TIMER = 0x97 # Set digital timer
549
CEC_OPCODE_SET_EXTERNAL_TIMER = 0xA2 # Set external timer
550
CEC_OPCODE_SET_TIMER_PROGRAM_TITLE = 0x67 # Set timer program title
551
CEC_OPCODE_TIMER_CLEARED_STATUS = 0x43 # Timer cleared status
552
CEC_OPCODE_TIMER_STATUS = 0x35 # Timer status
553
554
# Deck Control
555
CEC_OPCODE_DECK_CONTROL = 0x42 # Deck control command
556
CEC_OPCODE_DECK_STATUS = 0x1B # Deck status report
557
CEC_OPCODE_GIVE_DECK_STATUS = 0x1A # Request deck status
558
CEC_OPCODE_PLAY = 0x41 # Play command
559
560
# Tuner Control
561
CEC_OPCODE_GIVE_TUNER_DEVICE_STATUS = 0x08 # Request tuner status
562
CEC_OPCODE_SELECT_ANALOGUE_SERVICE = 0x92 # Select analogue service
563
CEC_OPCODE_SELECT_DIGITAL_SERVICE = 0x93 # Select digital service
564
CEC_OPCODE_TUNER_DEVICE_STATUS = 0x07 # Report tuner status
565
CEC_OPCODE_TUNER_STEP_DECREMENT = 0x06 # Tuner step down
566
CEC_OPCODE_TUNER_STEP_INCREMENT = 0x05 # Tuner step up
567
568
# Menu/Language Control
569
CEC_OPCODE_GET_MENU_LANGUAGE = 0x91 # Request menu language
570
CEC_OPCODE_SET_MENU_LANGUAGE = 0x32 # Set menu language
571
CEC_OPCODE_SET_OSD_STRING = 0x64 # Set OSD string
572
573
# Vendor Commands
574
CEC_OPCODE_VENDOR_COMMAND = 0x89 # Vendor-specific command
575
CEC_OPCODE_VENDOR_COMMAND_WITH_ID = 0xA0 # Vendor command with ID
576
CEC_OPCODE_VENDOR_REMOTE_BUTTON_DOWN = 0x8A # Vendor remote button down
577
CEC_OPCODE_VENDOR_REMOTE_BUTTON_UP = 0x8B # Vendor remote button up
578
579
# Audio Return Channel (ARC)
580
CEC_OPCODE_START_ARC = 0xC0 # Start audio return channel
581
CEC_OPCODE_REPORT_ARC_STARTED = 0xC1 # Report ARC started
582
CEC_OPCODE_REPORT_ARC_ENDED = 0xC2 # Report ARC ended
583
CEC_OPCODE_REQUEST_ARC_START = 0xC3 # Request ARC start
584
CEC_OPCODE_REQUEST_ARC_END = 0xC4 # Request ARC end
585
CEC_OPCODE_END_ARC = 0xC5 # End audio return channel
586
587
# Audio Rate Control
588
CEC_OPCODE_SET_AUDIO_RATE = 0x9A # Set audio sample rate
589
590
# Special/Advanced
591
CEC_OPCODE_CDC = 0xF8 # Capability Discovery and Control
592
CEC_OPCODE_NONE = 0xFD # No operation
593
594
# General
595
CEC_OPCODE_FEATURE_ABORT = 0x00 # Feature not supported
596
CEC_OPCODE_ABORT = 0xFF # Abort message
597
```
598
599
### Library Feature Constants
600
601
Feature availability constants for checking library capabilities:
602
603
```python { .api }
604
HAVE_CEC_ADAPTER_DESCRIPTOR = 0 or 1 # Whether library supports adapter descriptors
605
```
606
607
## Advanced Usage Examples
608
609
### Event Handling
610
611
```python
612
import cec
613
614
def log_callback(event, level, time, message):
615
"""Handle log messages."""
616
print(f"CEC Log [{level}]: {message}")
617
618
def command_callback(event, source, destination, opcode, parameters):
619
"""Handle CEC commands."""
620
print(f"CEC Command: {source} -> {destination}, opcode: {opcode}")
621
622
def key_callback(event, key_code, duration):
623
"""Handle key press events."""
624
print(f"Key pressed: {key_code}, duration: {duration}")
625
626
# Register callbacks
627
cec.add_callback(log_callback, cec.EVENT_LOG)
628
cec.add_callback(command_callback, cec.EVENT_COMMAND)
629
cec.add_callback(key_callback, cec.EVENT_KEYPRESS)
630
631
# Initialize and use CEC
632
cec.init()
633
634
# Your application logic here...
635
636
# Remove callbacks when done
637
cec.remove_callback(log_callback, cec.EVENT_LOG)
638
cec.remove_callback(command_callback, cec.EVENT_COMMAND)
639
cec.remove_callback(key_callback, cec.EVENT_KEYPRESS)
640
```
641
642
### Device Enumeration and Control
643
644
```python
645
import cec
646
647
# Initialize CEC
648
adapters = cec.list_adapters()
649
if adapters:
650
print(f"Available adapters: {adapters}")
651
cec.init(adapters[0]) # Use first adapter
652
else:
653
cec.init() # Use default adapter
654
655
# Discover devices
656
devices = cec.list_devices()
657
print(f"Discovered devices: {devices}")
658
659
# Create device objects and query information
660
for device_addr in devices:
661
device = cec.Device(device_addr)
662
print(f"Device {device_addr}:")
663
print(f" Physical Address: {device.physical_address}")
664
print(f" OSD Name: {device.osd_string}")
665
print(f" Vendor: {device.vendor}")
666
print(f" CEC Version: {device.cec_version}")
667
print(f" Language: {device.language}")
668
print(f" Is On: {device.is_on()}")
669
print(f" Is Active: {device.is_active()}")
670
```
671
672
### Raw Command Transmission
673
674
```python
675
import cec
676
677
cec.init()
678
679
# Set arbitrary device as active source (physical address 2.0.0.0)
680
destination = cec.CECDEVICE_BROADCAST
681
opcode = cec.CEC_OPCODE_ACTIVE_SOURCE
682
parameters = b'\x20\x00' # Physical address 2.0.0.0
683
684
success = cec.transmit(destination, opcode, parameters)
685
print(f"Command transmission {'successful' if success else 'failed'}")
686
687
# Send standby command to TV
688
tv_addr = cec.CECDEVICE_TV
689
standby_opcode = cec.CEC_OPCODE_STANDBY
690
success = cec.transmit(tv_addr, standby_opcode, b'')
691
print(f"Standby command {'sent' if success else 'failed'}")
692
```
693
694
### Configuration Management
695
696
```python
697
import cec
698
699
cec.init()
700
701
# Check if adapter supports configuration persistence
702
if cec.can_persist_config():
703
print("Adapter supports configuration persistence")
704
705
# Set physical address
706
success = cec.set_physical_addr(0x2000) # Physical address 2.0.0.0
707
if success:
708
print("Physical address set")
709
710
# Persist configuration
711
if cec.persist_config():
712
print("Configuration persisted to adapter")
713
else:
714
print("Failed to persist configuration")
715
else:
716
print("Failed to set physical address")
717
else:
718
print("Adapter does not support configuration persistence")
719
```
720
721
## Error Handling
722
723
The CEC library may raise exceptions or return False/None for failed operations:
724
725
- **RuntimeError**: Raised during initialization failures or critical errors
726
- **False return values**: Most functions return False when commands fail to send
727
- **None return values**: Some functions return None when unable to retrieve information
728
- **Empty lists**: Device/adapter listing functions return empty lists when none found
729
730
Always check return values and handle potential exceptions:
731
732
```python
733
import cec
734
735
try:
736
# Initialize with error handling
737
adapters = cec.list_adapters()
738
if not adapters:
739
print("No CEC adapters found")
740
exit(1)
741
742
cec.init(adapters[0])
743
print("CEC initialized successfully")
744
745
# Device operations with return value checking
746
tv = cec.Device(cec.CECDEVICE_TV)
747
748
if tv.power_on():
749
print("TV power on command sent")
750
else:
751
print("Failed to send TV power on command")
752
753
# Volume control with return value checking
754
if cec.volume_up():
755
print("Volume up command sent")
756
else:
757
print("Failed to send volume up command")
758
759
except RuntimeError as e:
760
print(f"CEC initialization failed: {e}")
761
except Exception as e:
762
print(f"Unexpected error: {e}")
763
```
764
765
## Platform Considerations
766
767
- **Linux**: Requires libcec development libraries and appropriate permissions for CEC device access
768
- **Windows**: Requires building libcec from source as binary distributions don't include required development files
769
- **macOS**: Install libcec via Homebrew (`brew install libcec`)
770
- **Raspberry Pi**: Built-in CEC support, no additional hardware required
771
- **Hardware**: Most computer graphics cards don't support CEC; requires dedicated CEC adapter (Pulse-Eight USB-CEC) or compatible hardware