Python bindings for libcec to control CEC-compliant HDMI devices from Python scripts
npx @tessl/cli install tessl/pypi-cec@0.2.0Python 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.
pip install cecimport cecimport cec
# Initialize CEC library with default adapter
cec.init()
# Create a device object for the TV
tv = cec.Device(cec.CECDEVICE_TV)
# Control the TV
tv.power_on()
print("TV is on:", tv.is_on())
# Volume control
cec.volume_up()
cec.volume_down()
cec.toggle_mute()
# Clean shutdown (when supported)
# cec.close() # Not implemented in current versionInitialize and manage CEC adapters for communication with the CEC bus.
def list_adapters():
"""
List available CEC adapters on the system.
Returns:
list: Available adapter names/paths
"""
def init():
"""
Initialize CEC library with the default adapter.
Returns:
bool: True if initialization successful
Raises:
RuntimeError: If initialization fails
"""
def init(adapter):
"""
Initialize CEC library with a specific adapter.
Args:
adapter (str): Adapter name/path from list_adapters()
Returns:
bool: True if initialization successful
Raises:
RuntimeError: If initialization fails
"""Discover and enumerate CEC devices on the bus.
def list_devices():
"""
List all discovered CEC devices on the bus.
Returns:
dict: Dictionary mapping device logical addresses (int) to Device objects
"""Register and manage event callbacks for CEC events like key presses, commands, and state changes.
def add_callback(handler, events):
"""
Add an event callback handler.
Args:
handler (callable): Function to handle events, signature varies by event type
events (int): Bitmask of event types to listen for
Returns:
bool: True if callback was registered successfully
"""
def remove_callback(handler, events):
"""
Remove an event callback handler.
Args:
handler (callable): Previously registered callback function
events (int): Bitmask of event types to stop listening for
Returns:
bool: True if callback was removed successfully
"""Send raw CEC commands directly to devices on the bus.
def transmit(destination, opcode, parameters=None, initiator=None):
"""
Transmit a raw CEC command to a specific device.
Args:
destination (int): Target device logical address (0-15)
opcode (int): CEC opcode for the command
parameters (bytes, optional): Command parameters as byte string
initiator (int, optional): Source logical address (0-15). If not specified, uses primary adapter address
Returns:
bool: True if transmission successful
"""Manage which device is the active source on the CEC bus.
def is_active_source(addr):
"""
Check if device at the given address is the active source.
Args:
addr (int): Device logical address
Returns:
bool: True if device is active source
"""
def set_active_source():
"""
Set the default device as the active source.
Returns:
bool: True if successful
"""
def set_active_source(device_type):
"""
Set a specific device type as the active source.
Args:
device_type (int): CEC device type constant
Returns:
bool: True if successful
"""Control audio volume and mute state through CEC.
def volume_up():
"""
Increase the volume via CEC.
Returns:
bool: True if command sent successfully
"""
def volume_down():
"""
Decrease the volume via CEC.
Returns:
bool: True if command sent successfully
"""
def toggle_mute():
"""
Toggle mute state via CEC.
Available in libcec 2.0+ only.
Returns:
bool: True if command sent successfully
"""Configure HDMI physical addresses and stream paths.
def set_stream_path(path):
"""
Set the HDMI stream path.
Args:
path (int): Physical address as integer
Returns:
bool: True if successful
"""
def set_physical_addr(addr):
"""
Set the HDMI physical address.
Args:
addr (int): Physical address as integer
Returns:
bool: True if successful
"""Manage CEC configuration persistence to adapter hardware.
def can_persist_config():
"""
Check if the current adapter supports configuration persistence.
Returns:
bool: True if adapter can persist configuration
"""
def persist_config():
"""
Persist the current CEC configuration to the adapter.
Returns:
bool: True if configuration was persisted successfully
"""
def set_port(device, port):
"""
Set upstream HDMI port for a device.
Args:
device (int): Device logical address
port (int): HDMI port number
Returns:
bool: True if successful
"""The Device class represents individual CEC devices and provides methods for device-specific control.
class Device:
"""
Represents a CEC device on the bus.
"""
def __init__(self, id):
"""
Create a Device object for the given logical address.
Args:
id (int): Logical address of the device (0-15)
"""
# Read-only properties
@property
def address(self):
"""
Logical address of the device.
Returns:
int: Device logical address (0-15)
"""
@property
def physical_address(self):
"""
Physical address of the device in HDMI topology.
Returns:
int: Physical address as integer
"""
@property
def vendor(self):
"""
Vendor ID of the device.
Returns:
int: Vendor identification number
"""
@property
def osd_string(self):
"""
On-Screen Display name of the device.
Returns:
str: Device OSD name
"""
@property
def cec_version(self):
"""
CEC version supported by the device.
Returns:
int: CEC version number
"""
@property
def language(self):
"""
Menu language of the device.
Returns:
str: ISO language code
"""
# Methods
def is_on(self):
"""
Get the power status of the device.
Returns:
bool: True if device is powered on
"""
def power_on(self):
"""
Power on the device.
Returns:
bool: True if command sent successfully
"""
def standby(self):
"""
Put the device into standby mode.
Returns:
bool: True if command sent successfully
"""
def is_active(self):
"""
Check if this device is the active source.
Returns:
bool: True if device is active source
"""
def set_av_input(self, input):
"""
Select AV input on the device.
Args:
input (int): Input number/identifier
Returns:
bool: True if command sent successfully
"""
def set_audio_input(self, input):
"""
Select audio input on the device.
Args:
input (int): Audio input number/identifier
Returns:
bool: True if command sent successfully
"""
def transmit(self, opcode, parameters):
"""
Transmit a raw CEC command to this device.
Args:
opcode (int): CEC opcode for the command
parameters (bytes): Command parameters as byte string
Returns:
bool: True if transmission successful
"""Event type constants for use with callback registration:
EVENT_LOG = 1 # Log message events
EVENT_KEYPRESS = 2 # Key press events
EVENT_COMMAND = 4 # CEC command events
EVENT_CONFIG_CHANGE = 8 # Configuration change events (not implemented)
EVENT_ALERT = 16 # Alert events
EVENT_MENU_CHANGED = 32 # Menu state change events
EVENT_ACTIVATED = 64 # Device activation events
EVENT_ALL = 127 # All event types combinedAlert type constants for EVENT_ALERT callbacks:
CEC_ALERT_SERVICE_DEVICE = 1 # Service device alert
CEC_ALERT_CONNECTION_LOST = 2 # Connection lost alert
CEC_ALERT_PERMISSION_ERROR = 3 # Permission error alert
CEC_ALERT_PORT_BUSY = 4 # Port busy alert
CEC_ALERT_PHYSICAL_ADDRESS_ERROR = 5 # Physical address error alert
CEC_ALERT_TV_POLL_FAILED = 6 # TV polling failed alertMenu state constants for EVENT_MENU_CHANGED callbacks:
CEC_MENU_STATE_ACTIVATED = 0 # Menu activated state
CEC_MENU_STATE_DEACTIVATED = 1 # Menu deactivated stateDevice type constants for device classification:
CEC_DEVICE_TYPE_TV = 0 # TV device type
CEC_DEVICE_TYPE_RECORDING_DEVICE = 1 # Recording device type
CEC_DEVICE_TYPE_RESERVED = 2 # Reserved device type
CEC_DEVICE_TYPE_TUNER = 3 # Tuner device type
CEC_DEVICE_TYPE_PLAYBACK_DEVICE = 4 # Playback device type
CEC_DEVICE_TYPE_AUDIO_SYSTEM = 5 # Audio system device typeLogical address constants for device identification:
CECDEVICE_UNKNOWN = -1 # Unknown device address
CECDEVICE_TV = 0 # TV logical address
CECDEVICE_RECORDINGDEVICE1 = 1 # Recording device 1 address
CECDEVICE_RECORDINGDEVICE2 = 2 # Recording device 2 address
CECDEVICE_TUNER1 = 3 # Tuner 1 address
CECDEVICE_PLAYBACKDEVICE1 = 4 # Playback device 1 address
CECDEVICE_AUDIOSYSTEM = 5 # Audio system address
CECDEVICE_TUNER2 = 6 # Tuner 2 address
CECDEVICE_TUNER3 = 7 # Tuner 3 address
CECDEVICE_PLAYBACKDEVICE2 = 8 # Playback device 2 address
CECDEVICE_RECORDINGDEVICE3 = 9 # Recording device 3 address
CECDEVICE_TUNER4 = 10 # Tuner 4 address
CECDEVICE_PLAYBACKDEVICE3 = 11 # Playback device 3 address
CECDEVICE_RESERVED1 = 12 # Reserved address 1
CECDEVICE_RESERVED2 = 13 # Reserved address 2
CECDEVICE_FREEUSE = 14 # Free use address
CECDEVICE_UNREGISTERED = 15 # Unregistered device address
CECDEVICE_BROADCAST = 15 # Broadcast addressCEC command opcodes for raw communication (selection of commonly used opcodes):
# Power Management
CEC_OPCODE_STANDBY = 0x36 # Put device in standby
CEC_OPCODE_IMAGE_VIEW_ON = 0x04 # Turn on and show image
CEC_OPCODE_TEXT_VIEW_ON = 0x0D # Turn on and show text
CEC_OPCODE_GIVE_DEVICE_POWER_STATUS = 0x8F # Request power status
CEC_OPCODE_REPORT_POWER_STATUS = 0x90 # Report power status
# Active Source Management
CEC_OPCODE_ACTIVE_SOURCE = 0x82 # Set active source
CEC_OPCODE_INACTIVE_SOURCE = 0x9D # Set inactive source
CEC_OPCODE_REQUEST_ACTIVE_SOURCE = 0x85 # Request active source
CEC_OPCODE_SET_STREAM_PATH = 0x86 # Set stream path
CEC_OPCODE_ROUTING_CHANGE = 0x80 # Routing change
CEC_OPCODE_ROUTING_INFORMATION = 0x81 # Routing information
# Device Information
CEC_OPCODE_GIVE_PHYSICAL_ADDRESS = 0x83 # Request physical address
CEC_OPCODE_REPORT_PHYSICAL_ADDRESS = 0x84 # Report physical address
CEC_OPCODE_GIVE_OSD_NAME = 0x46 # Request OSD name
CEC_OPCODE_SET_OSD_NAME = 0x47 # Set OSD name
CEC_OPCODE_GIVE_DEVICE_VENDOR_ID = 0x8C # Request vendor ID
CEC_OPCODE_DEVICE_VENDOR_ID = 0x87 # Report vendor ID
CEC_OPCODE_GET_CEC_VERSION = 0x9F # Request CEC version
CEC_OPCODE_CEC_VERSION = 0x9E # Report CEC version
# Audio System
CEC_OPCODE_GIVE_AUDIO_STATUS = 0x71 # Request audio status
CEC_OPCODE_REPORT_AUDIO_STATUS = 0x7A # Report audio status
CEC_OPCODE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D # Request system audio mode
CEC_OPCODE_SYSTEM_AUDIO_MODE_STATUS = 0x7E # Report system audio mode
CEC_OPCODE_SET_SYSTEM_AUDIO_MODE = 0x72 # Set system audio mode
CEC_OPCODE_SYSTEM_AUDIO_MODE_REQUEST = 0x70 # Request system audio mode
# User Control
CEC_OPCODE_USER_CONTROL_PRESSED = 0x44 # User pressed button
CEC_OPCODE_USER_CONTROL_RELEASE = 0x45 # User released button
# Menu Control
CEC_OPCODE_MENU_REQUEST = 0x8D # Menu request
CEC_OPCODE_MENU_STATUS = 0x8E # Menu status
# Recording/Timer Control
CEC_OPCODE_RECORD_OFF = 0x0B # Stop recording
CEC_OPCODE_RECORD_ON = 0x09 # Start recording
CEC_OPCODE_RECORD_STATUS = 0x0A # Report recording status
CEC_OPCODE_RECORD_TV_SCREEN = 0x0F # Record TV screen
CEC_OPCODE_CLEAR_ANALOGUE_TIMER = 0x33 # Clear analogue timer
CEC_OPCODE_CLEAR_DIGITAL_TIMER = 0x99 # Clear digital timer
CEC_OPCODE_CLEAR_EXTERNAL_TIMER = 0xA1 # Clear external timer
CEC_OPCODE_SET_ANALOGUE_TIMER = 0x34 # Set analogue timer
CEC_OPCODE_SET_DIGITAL_TIMER = 0x97 # Set digital timer
CEC_OPCODE_SET_EXTERNAL_TIMER = 0xA2 # Set external timer
CEC_OPCODE_SET_TIMER_PROGRAM_TITLE = 0x67 # Set timer program title
CEC_OPCODE_TIMER_CLEARED_STATUS = 0x43 # Timer cleared status
CEC_OPCODE_TIMER_STATUS = 0x35 # Timer status
# Deck Control
CEC_OPCODE_DECK_CONTROL = 0x42 # Deck control command
CEC_OPCODE_DECK_STATUS = 0x1B # Deck status report
CEC_OPCODE_GIVE_DECK_STATUS = 0x1A # Request deck status
CEC_OPCODE_PLAY = 0x41 # Play command
# Tuner Control
CEC_OPCODE_GIVE_TUNER_DEVICE_STATUS = 0x08 # Request tuner status
CEC_OPCODE_SELECT_ANALOGUE_SERVICE = 0x92 # Select analogue service
CEC_OPCODE_SELECT_DIGITAL_SERVICE = 0x93 # Select digital service
CEC_OPCODE_TUNER_DEVICE_STATUS = 0x07 # Report tuner status
CEC_OPCODE_TUNER_STEP_DECREMENT = 0x06 # Tuner step down
CEC_OPCODE_TUNER_STEP_INCREMENT = 0x05 # Tuner step up
# Menu/Language Control
CEC_OPCODE_GET_MENU_LANGUAGE = 0x91 # Request menu language
CEC_OPCODE_SET_MENU_LANGUAGE = 0x32 # Set menu language
CEC_OPCODE_SET_OSD_STRING = 0x64 # Set OSD string
# Vendor Commands
CEC_OPCODE_VENDOR_COMMAND = 0x89 # Vendor-specific command
CEC_OPCODE_VENDOR_COMMAND_WITH_ID = 0xA0 # Vendor command with ID
CEC_OPCODE_VENDOR_REMOTE_BUTTON_DOWN = 0x8A # Vendor remote button down
CEC_OPCODE_VENDOR_REMOTE_BUTTON_UP = 0x8B # Vendor remote button up
# Audio Return Channel (ARC)
CEC_OPCODE_START_ARC = 0xC0 # Start audio return channel
CEC_OPCODE_REPORT_ARC_STARTED = 0xC1 # Report ARC started
CEC_OPCODE_REPORT_ARC_ENDED = 0xC2 # Report ARC ended
CEC_OPCODE_REQUEST_ARC_START = 0xC3 # Request ARC start
CEC_OPCODE_REQUEST_ARC_END = 0xC4 # Request ARC end
CEC_OPCODE_END_ARC = 0xC5 # End audio return channel
# Audio Rate Control
CEC_OPCODE_SET_AUDIO_RATE = 0x9A # Set audio sample rate
# Special/Advanced
CEC_OPCODE_CDC = 0xF8 # Capability Discovery and Control
CEC_OPCODE_NONE = 0xFD # No operation
# General
CEC_OPCODE_FEATURE_ABORT = 0x00 # Feature not supported
CEC_OPCODE_ABORT = 0xFF # Abort messageFeature availability constants for checking library capabilities:
HAVE_CEC_ADAPTER_DESCRIPTOR = 0 or 1 # Whether library supports adapter descriptorsimport cec
def log_callback(event, level, time, message):
"""Handle log messages."""
print(f"CEC Log [{level}]: {message}")
def command_callback(event, source, destination, opcode, parameters):
"""Handle CEC commands."""
print(f"CEC Command: {source} -> {destination}, opcode: {opcode}")
def key_callback(event, key_code, duration):
"""Handle key press events."""
print(f"Key pressed: {key_code}, duration: {duration}")
# Register callbacks
cec.add_callback(log_callback, cec.EVENT_LOG)
cec.add_callback(command_callback, cec.EVENT_COMMAND)
cec.add_callback(key_callback, cec.EVENT_KEYPRESS)
# Initialize and use CEC
cec.init()
# Your application logic here...
# Remove callbacks when done
cec.remove_callback(log_callback, cec.EVENT_LOG)
cec.remove_callback(command_callback, cec.EVENT_COMMAND)
cec.remove_callback(key_callback, cec.EVENT_KEYPRESS)import cec
# Initialize CEC
adapters = cec.list_adapters()
if adapters:
print(f"Available adapters: {adapters}")
cec.init(adapters[0]) # Use first adapter
else:
cec.init() # Use default adapter
# Discover devices
devices = cec.list_devices()
print(f"Discovered devices: {devices}")
# Create device objects and query information
for device_addr in devices:
device = cec.Device(device_addr)
print(f"Device {device_addr}:")
print(f" Physical Address: {device.physical_address}")
print(f" OSD Name: {device.osd_string}")
print(f" Vendor: {device.vendor}")
print(f" CEC Version: {device.cec_version}")
print(f" Language: {device.language}")
print(f" Is On: {device.is_on()}")
print(f" Is Active: {device.is_active()}")import cec
cec.init()
# Set arbitrary device as active source (physical address 2.0.0.0)
destination = cec.CECDEVICE_BROADCAST
opcode = cec.CEC_OPCODE_ACTIVE_SOURCE
parameters = b'\x20\x00' # Physical address 2.0.0.0
success = cec.transmit(destination, opcode, parameters)
print(f"Command transmission {'successful' if success else 'failed'}")
# Send standby command to TV
tv_addr = cec.CECDEVICE_TV
standby_opcode = cec.CEC_OPCODE_STANDBY
success = cec.transmit(tv_addr, standby_opcode, b'')
print(f"Standby command {'sent' if success else 'failed'}")import cec
cec.init()
# Check if adapter supports configuration persistence
if cec.can_persist_config():
print("Adapter supports configuration persistence")
# Set physical address
success = cec.set_physical_addr(0x2000) # Physical address 2.0.0.0
if success:
print("Physical address set")
# Persist configuration
if cec.persist_config():
print("Configuration persisted to adapter")
else:
print("Failed to persist configuration")
else:
print("Failed to set physical address")
else:
print("Adapter does not support configuration persistence")The CEC library may raise exceptions or return False/None for failed operations:
Always check return values and handle potential exceptions:
import cec
try:
# Initialize with error handling
adapters = cec.list_adapters()
if not adapters:
print("No CEC adapters found")
exit(1)
cec.init(adapters[0])
print("CEC initialized successfully")
# Device operations with return value checking
tv = cec.Device(cec.CECDEVICE_TV)
if tv.power_on():
print("TV power on command sent")
else:
print("Failed to send TV power on command")
# Volume control with return value checking
if cec.volume_up():
print("Volume up command sent")
else:
print("Failed to send volume up command")
except RuntimeError as e:
print(f"CEC initialization failed: {e}")
except Exception as e:
print(f"Unexpected error: {e}")brew install libcec)