"""Support for Habitica devices.""" from collections import namedtuple import logging import voluptuous as vol from homeassistant.const import ( CONF_API_KEY, CONF_NAME, CONF_PATH, CONF_SENSORS, CONF_URL) from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession REQUIREMENTS = ['habitipy==0.2.0'] _LOGGER = logging.getLogger(__name__) CONF_API_USER = 'api_user' DEFAULT_URL = 'https://habitica.com' DOMAIN = 'habitica' ST = SensorType = namedtuple('SensorType', [ 'name', 'icon', 'unit', 'path' ]) SENSORS_TYPES = { 'name': ST('Name', None, '', ['profile', 'name']), 'hp': ST('HP', 'mdi:heart', 'HP', ['stats', 'hp']), 'maxHealth': ST('max HP', 'mdi:heart', 'HP', ['stats', 'maxHealth']), 'mp': ST('Mana', 'mdi:auto-fix', 'MP', ['stats', 'mp']), 'maxMP': ST('max Mana', 'mdi:auto-fix', 'MP', ['stats', 'maxMP']), 'exp': ST('EXP', 'mdi:star', 'EXP', ['stats', 'exp']), 'toNextLevel': ST( 'Next Lvl', 'mdi:star', 'EXP', ['stats', 'toNextLevel']), 'lvl': ST( 'Lvl', 'mdi:arrow-up-bold-circle-outline', 'Lvl', ['stats', 'lvl']), 'gp': ST('Gold', 'mdi:coin', 'Gold', ['stats', 'gp']), 'class': ST('Class', 'mdi:sword', '', ['stats', 'class']) } INSTANCE_SCHEMA = vol.Schema({ vol.Optional(CONF_URL, default=DEFAULT_URL): cv.url, vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_API_USER): cv.string, vol.Required(CONF_API_KEY): cv.string, vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)): vol.All(cv.ensure_list, vol.Unique(), [vol.In(list(SENSORS_TYPES))]), }) has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name # because we want a handy alias def has_all_unique_users(value): """Validate that all API users are unique.""" api_users = [user[CONF_API_USER] for user in value] has_unique_values(api_users) return value def has_all_unique_users_names(value): """Validate that all user's names are unique and set if any is set.""" names = [user.get(CONF_NAME) for user in value] if None in names and any(name is not None for name in names): raise vol.Invalid( 'user names of all users must be set if any is set') if not all(name is None for name in names): has_unique_values(names) return value INSTANCE_LIST_SCHEMA = vol.All( cv.ensure_list, has_all_unique_users, has_all_unique_users_names, [INSTANCE_SCHEMA]) CONFIG_SCHEMA = vol.Schema({ DOMAIN: INSTANCE_LIST_SCHEMA }, extra=vol.ALLOW_EXTRA) SERVICE_API_CALL = 'api_call' ATTR_NAME = CONF_NAME ATTR_PATH = CONF_PATH ATTR_ARGS = 'args' EVENT_API_CALL_SUCCESS = '{0}_{1}_{2}'.format( DOMAIN, SERVICE_API_CALL, 'success') SERVICE_API_CALL_SCHEMA = vol.Schema({ vol.Required(ATTR_NAME): str, vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]), vol.Optional(ATTR_ARGS): dict, }) async def async_setup(hass, config): """Set up the Habitica service.""" from habitipy.aio import HabitipyAsync conf = config[DOMAIN] data = hass.data[DOMAIN] = {} websession = async_get_clientsession(hass) class HAHabitipyAsync(HabitipyAsync): """Closure API class to hold session.""" def __call__(self, **kwargs): return super().__call__(websession, **kwargs) for instance in conf: url = instance[CONF_URL] username = instance[CONF_API_USER] password = instance[CONF_API_KEY] name = instance.get(CONF_NAME) config_dict = {'url': url, 'login': username, 'password': password} api = HAHabitipyAsync(config_dict) user = await api.user.get() if name is None: name = user['profile']['name'] data[name] = api if CONF_SENSORS in instance: hass.async_create_task( discovery.async_load_platform( hass, 'sensor', DOMAIN, {'name': name, 'sensors': instance[CONF_SENSORS]}, config)) async def handle_api_call(call): name = call.data[ATTR_NAME] path = call.data[ATTR_PATH] api = hass.data[DOMAIN].get(name) if api is None: _LOGGER.error("API_CALL: User '%s' not configured", name) return try: for element in path: api = api[element] except KeyError: _LOGGER.error( "API_CALL: Path %s is invalid for API on '{%s}' element", path, element) return kwargs = call.data.get(ATTR_ARGS, {}) data = await api(**kwargs) hass.bus.async_fire( EVENT_API_CALL_SUCCESS, {'name': name, 'path': path, 'data': data}) hass.services.async_register( DOMAIN, SERVICE_API_CALL, handle_api_call, schema=SERVICE_API_CALL_SCHEMA) return True