"""KIRA interface to receive UDP packets from an IR-IP bridge.""" import logging import os import pykira import voluptuous as vol from voluptuous.error import Error as VoluptuousError import yaml from homeassistant.const import ( CONF_CODE, CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, CONF_SENSORS, CONF_TYPE, EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN, ) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv DOMAIN = "kira" _LOGGER = logging.getLogger(__name__) DEFAULT_HOST = "0.0.0.0" DEFAULT_PORT = 65432 CONF_REPEAT = "repeat" CONF_REMOTES = "remotes" CONF_SENSOR = "sensor" CONF_REMOTE = "remote" CODES_YAML = f"{DOMAIN}_codes.yaml" CODE_SCHEMA = vol.Schema( { vol.Required(CONF_NAME): cv.string, vol.Required(CONF_CODE): cv.string, vol.Optional(CONF_TYPE): cv.string, vol.Optional(CONF_DEVICE): cv.string, vol.Optional(CONF_REPEAT): cv.positive_int, } ) SENSOR_SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DOMAIN): vol.Exclusive(cv.string, "sensors"), vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, } ) REMOTE_SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DOMAIN): vol.Exclusive(cv.string, "remotes"), vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, } ) CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { vol.Optional(CONF_SENSORS): [SENSOR_SCHEMA], vol.Optional(CONF_REMOTES): [REMOTE_SCHEMA], } ) }, extra=vol.ALLOW_EXTRA, ) def load_codes(path): """Load KIRA codes from specified file.""" codes = [] if os.path.exists(path): with open(path) as code_file: data = yaml.safe_load(code_file) or [] for code in data: try: codes.append(CODE_SCHEMA(code)) except VoluptuousError as exception: # keep going _LOGGER.warning("KIRA code invalid data: %s", exception) else: with open(path, "w") as code_file: code_file.write("") return codes def setup(hass, config): """Set up the KIRA component.""" sensors = config.get(DOMAIN, {}).get(CONF_SENSORS, []) remotes = config.get(DOMAIN, {}).get(CONF_REMOTES, []) # If no sensors or remotes were specified, add a sensor if not (sensors or remotes): sensors.append({}) codes = load_codes(hass.config.path(CODES_YAML)) hass.data[DOMAIN] = {CONF_SENSOR: {}, CONF_REMOTE: {}} def load_module(platform, idx, module_conf): """Set up the KIRA module and load platform.""" # note: module_name is not the HA device name. it's just a unique name # to ensure the component and platform can share information module_name = ("%s_%d" % (DOMAIN, idx)) if idx else DOMAIN device_name = module_conf.get(CONF_NAME, DOMAIN) port = module_conf.get(CONF_PORT, DEFAULT_PORT) host = module_conf.get(CONF_HOST, DEFAULT_HOST) if platform == CONF_SENSOR: module = pykira.KiraReceiver(host, port) module.start() else: module = pykira.KiraModule(host, port) hass.data[DOMAIN][platform][module_name] = module for code in codes: code_tuple = (code.get(CONF_NAME), code.get(CONF_DEVICE, STATE_UNKNOWN)) module.registerCode(code_tuple, code.get(CONF_CODE)) discovery.load_platform( hass, platform, DOMAIN, {"name": module_name, "device": device_name}, config ) for idx, module_conf in enumerate(sensors): load_module(CONF_SENSOR, idx, module_conf) for idx, module_conf in enumerate(remotes): load_module(CONF_REMOTE, idx, module_conf) def _stop_kira(_event): """Stop the KIRA receiver.""" for receiver in hass.data[DOMAIN][CONF_SENSOR].values(): receiver.stop() _LOGGER.info("Terminated receivers") hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _stop_kira) return True