def main(): tcp_adapter = TcpAdapter("192.168.1.3", name="HASS", activate_source=False) hdmi_network = HDMINetwork(tcp_adapter) hdmi_network.start() while True: for d in hdmi_network.devices: _LOGGER.info("Device: %s", d) time.sleep(7)
def test_devices(): loop = asyncio.get_event_loop() network = HDMINetwork( MockAdapter([ True, True, False, True, False, True, False, False, False, False, False, False, False, False, False, False, ]), scan_interval=0, loop=loop, ) network._scan_delay = 0 # network._adapter.set_command_callback(network.command_callback) network.init() network.scan() loop.run_until_complete(asyncio.sleep(0.1, loop)) loop.stop() loop.run_forever() for i in [0, 1, 3, 5]: assert HDMIDevice(i) in network.devices for i in [2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14]: assert HDMIDevice(i) not in network.devices for d in network.devices: d.stop() network.stop() loop.stop() loop.run_forever()
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(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 test_scan(): loop = asyncio.get_event_loop() network = HDMINetwork( MockAdapter([ True, True, False, True, False, True, False, False, False, False, False, False, False, False, False, False, ]), scan_interval=0, loop=loop, ) network._scan_delay = 0 # network._adapter.set_command_callback(network.command_callback) network.init() network.scan() loop.run_until_complete(asyncio.sleep(0.1, loop)) loop.stop() loop.run_forever() assert HDMIDevice(0) in network.devices device = network.get_device(0) assert "Test0" == device.osd_name assert 2 == device.power_status assert HDMIDevice(1) in network.devices device = network.get_device(1) assert "Test1" == device.osd_name assert 2 == device.power_status assert HDMIDevice(2) not in network.devices assert HDMIDevice(3) in network.devices device = network.get_device(3) assert "Test3" == device.osd_name assert 2 == device.power_status for d in network.devices: d.stop() network.stop() loop.stop() loop.run_forever()
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)
#!/usr/bin/env python import sys from flask import Flask, jsonify, request, abort from pycec.cec import CecAdapter from pycec.const import POWER_OFF, POWER_ON from pycec.network import HDMINetwork from encoder import HDMIDeviceEncoder app = Flask(__name__) app.json_encoder = HDMIDeviceEncoder network = HDMINetwork(CecAdapter("raspberry", activate_source=True)) network.start() @app.route('/api/v1.0/cec', methods=['GET']) def cec_list(): return jsonify({'devices': network.devices}) @app.route('/api/v1.0/cec/<int:device_id>', methods=['GET']) def cec_get(device_id): return jsonify(network.get_device(device_id)) @app.route('/api/v1.0/cec/<int:device_id>', methods=['PUT']) def cec_post(device_id): if not request.json or 'powerStatus' not in request.json: abort(400)
def main(): config = configure() # Configure logging setup_logger(config) transports = set() loop = asyncio.get_event_loop() network = HDMINetwork(CecAdapter("pyCEC", activate_source=False), loop=loop) class CECServerProtocol(asyncio.Protocol): transport = None buffer = '' def connection_made(self, transport): _LOGGER.info("Connection opened by %s", transport.get_extra_info('peername')) self.transport = transport transports.add(transport) def data_received(self, data): self.buffer += bytes.decode(data) for line in self.buffer.splitlines(keepends=True): if line.endswith('\n'): line = line.rstrip() if len(line) == 2: _LOGGER.info("Received poll %s from %s", line, self.transport.get_extra_info('peername')) d = CecCommand(line).dst t = network._adapter.poll_device(d) t.add_done_callback( functools.partial(_after_poll, d)) else: _LOGGER.info("Received command %s from %s", line, self.transport.get_extra_info('peername')) network.send_command(CecCommand(line)) self.buffer = '' else: self.buffer = line def connection_lost(self, exc): _LOGGER.info("Connection with %s lost", self.transport.get_extra_info('peername')) transports.remove(self.transport) def _after_poll(d, f): if f.result(): cmd = PollCommand(network._adapter.get_logical_address(), src=d) _send_command_to_tcp(cmd) def _send_command_to_tcp(command): for t in transports: _LOGGER.info("Sending %s to %s", command, t.get_extra_info('peername')) t.write(str.encode("%s\n" % command.raw)) network.set_command_callback(_send_command_to_tcp) loop.run_until_complete(network.async_init()) _LOGGER.info("CEC initialized... Starting server.") # Each client connection will create a new protocol instance coro = loop.create_server(CECServerProtocol, config['DEFAULT']['host'], int(config['DEFAULT']['port'])) server = loop.run_until_complete(coro) # Serve requests until Ctrl+C is pressed _LOGGER.info('Serving on {}'.format(server.sockets[0].getsockname())) if _LOGGER.level >= logging.DEBUG: loop.create_task(async_show_devices(network, loop)) try: loop.run_forever() except KeyboardInterrupt: pass # Close the server server.close() loop.run_until_complete(server.wait_closed()) loop.close()