def setup(opp: OpenPeerPower, base_config): # noqa: C901 """Set up the CEC capability.""" # Parse configuration into a dict of device name to physical address # represented as a list of four elements. device_aliases = {} devices = base_config[DOMAIN].get(CONF_DEVICES, {}) _LOGGER.debug("Parsing config %s", devices) device_aliases.update(parse_mapping(devices)) _LOGGER.debug("Parsed devices: %s", device_aliases) platform = base_config[DOMAIN].get(CONF_PLATFORM, SWITCH) loop = ( # Create own thread if more than 1 CPU opp.loop if multiprocessing.cpu_count() < 2 else None) host = base_config[DOMAIN].get(CONF_HOST) display_name = base_config[DOMAIN].get(CONF_DISPLAY_NAME, DEFAULT_DISPLAY_NAME) if host: adapter = TcpAdapter(host, name=display_name, activate_source=False) else: adapter = CecAdapter(name=display_name[:12], activate_source=False) hdmi_network = HDMINetwork(adapter, loop=loop) def _adapter_watchdog(now=None): _LOGGER.debug("Reached _adapter_watchdog") event.async_call_later(opp, WATCHDOG_INTERVAL, _adapter_watchdog) if not adapter.initialized: _LOGGER.info("Adapter not initialized; Trying to restart") opp.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE) adapter.init() hdmi_network.set_initialized_callback( partial(event.async_call_later, opp, WATCHDOG_INTERVAL, _adapter_watchdog)) def _volume(call): """Increase/decrease volume and mute/unmute system.""" mute_key_mapping = { ATTR_TOGGLE: KEY_MUTE_TOGGLE, ATTR_ON: KEY_MUTE_ON, ATTR_OFF: KEY_MUTE_OFF, } for cmd, att in call.data.items(): if cmd == CMD_UP: _process_volume(KEY_VOLUME_UP, att) elif cmd == CMD_DOWN: _process_volume(KEY_VOLUME_DOWN, att) elif cmd == CMD_MUTE: hdmi_network.send_command( KeyPressCommand(mute_key_mapping[att], dst=ADDR_AUDIOSYSTEM)) hdmi_network.send_command( KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) _LOGGER.info("Audio muted") else: _LOGGER.warning("Unknown command %s", cmd) def _process_volume(cmd, att): if isinstance(att, (str, )): att = att.strip() if att == CMD_PRESS: hdmi_network.send_command( KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) elif att == CMD_RELEASE: hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) else: att = 1 if att == "" else int(att) for _ in range(0, att): hdmi_network.send_command( KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) hdmi_network.send_command( KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) def _tx(call): """Send CEC command.""" data = call.data if ATTR_RAW in data: command = CecCommand(data[ATTR_RAW]) else: if ATTR_SRC in data: src = data[ATTR_SRC] else: src = ADDR_UNREGISTERED if ATTR_DST in data: dst = data[ATTR_DST] else: dst = ADDR_BROADCAST if ATTR_CMD in data: cmd = data[ATTR_CMD] else: _LOGGER.error("Attribute 'cmd' is missing") return False if ATTR_ATT in data: if isinstance(data[ATTR_ATT], (list, )): att = data[ATTR_ATT] else: att = reduce(lambda x, y: f"{x}:{y:x}", data[ATTR_ATT]) else: att = "" command = CecCommand(cmd, dst, src, att) hdmi_network.send_command(command) def _standby(call): hdmi_network.standby() def _power_on(call): hdmi_network.power_on() def _select_device(call): """Select the active device.""" addr = call.data[ATTR_DEVICE] if not addr: _LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE]) return if addr in device_aliases: addr = device_aliases[addr] else: entity = opp.states.get(addr) _LOGGER.debug("Selecting entity %s", entity) if entity is not None: addr = entity.attributes["physical_address"] _LOGGER.debug("Address acquired: %s", addr) if addr is None: _LOGGER.error("Device %s has not physical address", call.data[ATTR_DEVICE]) return if not isinstance(addr, (PhysicalAddress, )): addr = PhysicalAddress(addr) hdmi_network.active_source(addr) _LOGGER.info("Selected %s (%s)", call.data[ATTR_DEVICE], addr) def _update(call): """ Update if device update is needed. Called by service, requests CEC network to update data. """ hdmi_network.scan() def _new_device(device): """Handle new devices which are detected by HDMI network.""" key = f"{DOMAIN}.{device.name}" opp.data[key] = device ent_platform = base_config[DOMAIN][CONF_TYPES].get(key, platform) discovery.load_platform( opp, ent_platform, DOMAIN, discovered={ATTR_NEW: [key]}, opp_config=base_config, ) def _shutdown(call): hdmi_network.stop() def _start_cec(callback_event): """Register services and start HDMI network to watch for devices.""" opp.services.register(DOMAIN, SERVICE_SEND_COMMAND, _tx, SERVICE_SEND_COMMAND_SCHEMA) opp.services.register(DOMAIN, SERVICE_VOLUME, _volume, schema=SERVICE_VOLUME_SCHEMA) opp.services.register( DOMAIN, SERVICE_UPDATE_DEVICES, _update, schema=SERVICE_UPDATE_DEVICES_SCHEMA, ) opp.services.register(DOMAIN, SERVICE_POWER_ON, _power_on) opp.services.register(DOMAIN, SERVICE_STANDBY, _standby) opp.services.register(DOMAIN, SERVICE_SELECT_DEVICE, _select_device) hdmi_network.set_new_device_callback(_new_device) hdmi_network.start() opp.bus.listen_once(EVENT_OPENPEERPOWER_START, _start_cec) opp.bus.listen_once(EVENT_OPENPEERPOWER_STOP, _shutdown) return True
def setup(hass: HomeAssistant, base_config): """Setup CEC capability.""" from pycec.network import HDMINetwork from pycec.commands import CecCommand, KeyReleaseCommand, KeyPressCommand from pycec.const import KEY_VOLUME_UP, KEY_VOLUME_DOWN, KEY_MUTE_ON, \ KEY_MUTE_OFF, KEY_MUTE_TOGGLE, ADDR_AUDIOSYSTEM, ADDR_BROADCAST, \ ADDR_UNREGISTERED from pycec.cec import CecAdapter from pycec.tcp import TcpAdapter # Parse configuration into a dict of device name to physical address # represented as a list of four elements. device_aliases = {} devices = base_config[DOMAIN].get(CONF_DEVICES, {}) _LOGGER.debug("Parsing config %s", devices) device_aliases.update(parse_mapping(devices)) _LOGGER.debug("Parsed devices: %s", device_aliases) platform = base_config[DOMAIN].get(CONF_PLATFORM, SWITCH) loop = ( # Create own thread if more than 1 CPU hass.loop if multiprocessing.cpu_count() < 2 else None) host = base_config[DOMAIN].get(CONF_HOST, None) display_name = base_config[DOMAIN].get(CONF_DISPLAY_NAME, DEFAULT_DISPLAY_NAME) if host: adapter = TcpAdapter(host, name=display_name, activate_source=False) else: adapter = CecAdapter(name=display_name, activate_source=False) hdmi_network = HDMINetwork(adapter, loop=loop) def _volume(call): """Increase/decrease volume and mute/unmute system.""" mute_key_mapping = { ATTR_TOGGLE: KEY_MUTE_TOGGLE, ATTR_ON: KEY_MUTE_ON, ATTR_OFF: KEY_MUTE_OFF } for cmd, att in call.data.items(): if cmd == CMD_UP: _process_volume(KEY_VOLUME_UP, att) elif cmd == CMD_DOWN: _process_volume(KEY_VOLUME_DOWN, att) elif cmd == CMD_MUTE: hdmi_network.send_command( KeyPressCommand(mute_key_mapping[att], dst=ADDR_AUDIOSYSTEM)) hdmi_network.send_command( KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) _LOGGER.info("Audio muted") else: _LOGGER.warning("Unknown command %s", cmd) def _process_volume(cmd, att): if isinstance(att, (str, )): att = att.strip() if att == CMD_PRESS: hdmi_network.send_command( KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) elif att == CMD_RELEASE: hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) else: att = 1 if att == "" else int(att) for _ in range(0, att): hdmi_network.send_command( KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) hdmi_network.send_command( KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) def _tx(call): """Send CEC command.""" data = call.data if ATTR_RAW in data: command = CecCommand(data[ATTR_RAW]) else: if ATTR_SRC in data: src = data[ATTR_SRC] else: src = ADDR_UNREGISTERED if ATTR_DST in data: dst = data[ATTR_DST] else: dst = ADDR_BROADCAST if ATTR_CMD in data: cmd = data[ATTR_CMD] else: _LOGGER.error("Attribute 'cmd' is missing") return False if ATTR_ATT in data: if isinstance(data[ATTR_ATT], (list, )): att = data[ATTR_ATT] else: att = reduce(lambda x, y: "%s:%x" % (x, y), data[ATTR_ATT]) else: att = "" command = CecCommand(cmd, dst, src, att) hdmi_network.send_command(command) def _standby(call): hdmi_network.standby() def _power_on(call): hdmi_network.power_on() def _select_device(call): """Select the active device.""" from pycec.network import PhysicalAddress addr = call.data[ATTR_DEVICE] if not addr: _LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE]) return if addr in device_aliases: addr = device_aliases[addr] else: entity = hass.states.get(addr) _LOGGER.debug("Selecting entity %s", entity) if entity is not None: addr = entity.attributes['physical_address'] _LOGGER.debug("Address acquired: %s", addr) if addr is None: _LOGGER.error("Device %s has not physical address.", call.data[ATTR_DEVICE]) return if not isinstance(addr, (PhysicalAddress, )): addr = PhysicalAddress(addr) hdmi_network.active_source(addr) _LOGGER.info("Selected %s (%s)", call.data[ATTR_DEVICE], addr) def _update(call): """ Callback called when device update is needed. - called by service, requests CEC network to update data. """ hdmi_network.scan() def _new_device(device): """Called when new device is detected by HDMI network.""" key = DOMAIN + '.' + device.name hass.data[key] = device ent_platform = base_config[DOMAIN][CONF_TYPES].get(key, platform) discovery.load_platform(hass, ent_platform, DOMAIN, discovered={ATTR_NEW: [key]}, hass_config=base_config) def _shutdown(call): hdmi_network.stop() def _start_cec(event): """Register services and start HDMI network to watch for devices.""" descriptions = load_yaml_config_file( os.path.join(os.path.dirname(__file__), 'services.yaml'))[DOMAIN] hass.services.register(DOMAIN, SERVICE_SEND_COMMAND, _tx, descriptions[SERVICE_SEND_COMMAND], SERVICE_SEND_COMMAND_SCHEMA) hass.services.register(DOMAIN, SERVICE_VOLUME, _volume, descriptions[SERVICE_VOLUME], SERVICE_VOLUME_SCHEMA) hass.services.register(DOMAIN, SERVICE_UPDATE_DEVICES, _update, descriptions[SERVICE_UPDATE_DEVICES], SERVICE_UPDATE_DEVICES_SCHEMA) hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on) hass.services.register(DOMAIN, SERVICE_STANDBY, _standby) hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE, _select_device) hdmi_network.set_new_device_callback(_new_device) hdmi_network.start() hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_cec) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) return True
def setup(hass: HomeAssistant, base_config: ConfigType) -> bool: # noqa: C901 """Set up the CEC capability.""" # Parse configuration into a dict of device name to physical address # represented as a list of four elements. device_aliases = {} devices = base_config[DOMAIN].get(CONF_DEVICES, {}) _LOGGER.debug("Parsing config %s", devices) device_aliases.update(parse_mapping(devices)) _LOGGER.debug("Parsed devices: %s", device_aliases) platform = base_config[DOMAIN].get(CONF_PLATFORM, SWITCH) loop = ( # Create own thread if more than 1 CPU hass.loop if multiprocessing.cpu_count() < 2 else None) host = base_config[DOMAIN].get(CONF_HOST) display_name = base_config[DOMAIN].get(CONF_DISPLAY_NAME, DEFAULT_DISPLAY_NAME) if host: adapter = TcpAdapter(host, name=display_name, activate_source=False) else: adapter = CecAdapter(name=display_name[:12], activate_source=False) hdmi_network = HDMINetwork(adapter, loop=loop) def _adapter_watchdog(now=None): _LOGGER.debug("Reached _adapter_watchdog") event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog) if not adapter.initialized: _LOGGER.info("Adapter not initialized; Trying to restart") hass.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE) adapter.init() @callback def _async_initialized_callback(*_: Any): """Add watchdog on initialization.""" return event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog) hdmi_network.set_initialized_callback(_async_initialized_callback) def _volume(call: ServiceCall) -> None: """Increase/decrease volume and mute/unmute system.""" mute_key_mapping = { ATTR_TOGGLE: KEY_MUTE_TOGGLE, ATTR_ON: KEY_MUTE_ON, ATTR_OFF: KEY_MUTE_OFF, } for cmd, att in call.data.items(): if cmd == CMD_UP: _process_volume(KEY_VOLUME_UP, att) elif cmd == CMD_DOWN: _process_volume(KEY_VOLUME_DOWN, att) elif cmd == CMD_MUTE: hdmi_network.send_command( KeyPressCommand(mute_key_mapping[att], dst=ADDR_AUDIOSYSTEM)) hdmi_network.send_command( KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) _LOGGER.info("Audio muted") else: _LOGGER.warning("Unknown command %s", cmd) def _process_volume(cmd, att): if isinstance(att, (str, )): att = att.strip() if att == CMD_PRESS: hdmi_network.send_command( KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) elif att == CMD_RELEASE: hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) else: att = 1 if att == "" else int(att) for _ in range(0, att): hdmi_network.send_command( KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) hdmi_network.send_command( KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) def _tx(call: ServiceCall) -> None: """Send CEC command.""" data = call.data if ATTR_RAW in data: command = CecCommand(data[ATTR_RAW]) else: if ATTR_SRC in data: src = data[ATTR_SRC] else: src = ADDR_UNREGISTERED if ATTR_DST in data: dst = data[ATTR_DST] else: dst = ADDR_BROADCAST if ATTR_CMD in data: cmd = data[ATTR_CMD] else: _LOGGER.error("Attribute 'cmd' is missing") return if ATTR_ATT in data: if isinstance(data[ATTR_ATT], (list, )): att = data[ATTR_ATT] else: att = reduce(lambda x, y: f"{x}:{y:x}", data[ATTR_ATT]) else: att = "" command = CecCommand(cmd, dst, src, att) hdmi_network.send_command(command) def _standby(call: ServiceCall) -> None: hdmi_network.standby() def _power_on(call: ServiceCall) -> None: hdmi_network.power_on() def _select_device(call: ServiceCall) -> None: """Select the active device.""" if not (addr := call.data[ATTR_DEVICE]): _LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE]) return if addr in device_aliases: addr = device_aliases[addr] else: entity = hass.states.get(addr) _LOGGER.debug("Selecting entity %s", entity) if entity is not None: addr = entity.attributes["physical_address"] _LOGGER.debug("Address acquired: %s", addr) if addr is None: _LOGGER.error("Device %s has not physical address", call.data[ATTR_DEVICE]) return if not isinstance(addr, (PhysicalAddress, )): addr = PhysicalAddress(addr) hdmi_network.active_source(addr) _LOGGER.info("Selected %s (%s)", call.data[ATTR_DEVICE], addr)