diff --git a/CODEOWNERS b/CODEOWNERS index c8a391fd7dc..f7a2d57c871 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -550,8 +550,8 @@ build.json @home-assistant/supervisor /tests/components/group/ @home-assistant/core /homeassistant/components/guardian/ @bachya /tests/components/guardian/ @bachya -/homeassistant/components/habitica/ @ASMfreaK @leikoilja -/tests/components/habitica/ @ASMfreaK @leikoilja +/homeassistant/components/habitica/ @ASMfreaK @leikoilja @tr4nt0r +/tests/components/habitica/ @ASMfreaK @leikoilja @tr4nt0r /homeassistant/components/hardkernel/ @home-assistant/core /tests/components/hardkernel/ @home-assistant/core /homeassistant/components/hardware/ @home-assistant/core diff --git a/homeassistant/components/habitica/const.py b/homeassistant/components/habitica/const.py index 1379f0a6447..13babdf458a 100644 --- a/homeassistant/components/habitica/const.py +++ b/homeassistant/components/habitica/const.py @@ -15,3 +15,6 @@ ATTR_ARGS = "args" # event constants EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success" ATTR_DATA = "data" + +MANUFACTURER = "HabitRPG, Inc." +NAME = "Habitica" diff --git a/homeassistant/components/habitica/icons.json b/homeassistant/components/habitica/icons.json index 4e5831c4e82..24a832139ca 100644 --- a/homeassistant/components/habitica/icons.json +++ b/homeassistant/components/habitica/icons.json @@ -1,5 +1,51 @@ { "services": { "api_call": "mdi:console" + }, + "entity": { + "sensor": { + "name": { + "default": "mdi:account-circle" + }, + "hp": { + "default": "mdi:heart", + "state": { + "0": "mdi:skull-outline" + } + }, + "maxhealth": { + "default": "mdi:heart" + }, + "mp": { + "default": "mdi:flask", + "state": { + "0": "mdi:flask-empty-outline" + } + }, + "maxmp": { + "default": "mdi:flask" + }, + "exp": { + "default": "mdi:star-four-points" + }, + "tonextlevel": { + "default": "mdi:star-four-points" + }, + "lvl": { + "default": "mdi:crown-circle" + }, + "gp": { + "default": "mdi:sack" + }, + "class": { + "default": "mdi:sword", + "state": { + "warrior": "mdi:sword", + "healer": "mdi:shield", + "wizard": "mdi:wizard-hat", + "rogue": "mdi:ninja" + } + } + } } } diff --git a/homeassistant/components/habitica/manifest.json b/homeassistant/components/habitica/manifest.json index f5f746c979d..1250e6d223f 100644 --- a/homeassistant/components/habitica/manifest.json +++ b/homeassistant/components/habitica/manifest.json @@ -1,7 +1,7 @@ { "domain": "habitica", "name": "Habitica", - "codeowners": ["@ASMfreaK", "@leikoilja"], + "codeowners": ["@ASMfreaK", "@leikoilja", "@tr4nt0r"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/habitica", "iot_class": "cloud_polling", diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index 4d48ec199ec..6278d901143 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -3,20 +3,22 @@ from __future__ import annotations from collections import namedtuple +from dataclasses import dataclass from datetime import timedelta from http import HTTPStatus import logging from aiohttp import ClientResponseError -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, CONF_URL from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import Throttle -from .const import DOMAIN +from .const import DOMAIN, MANUFACTURER, NAME _LOGGER = logging.getLogger(__name__) @@ -39,6 +41,78 @@ SENSORS_TYPES = { "class": SensorType("Class", "mdi:sword", None, ["stats", "class"]), } + +@dataclass(kw_only=True, frozen=True) +class HabitipySensorEntityDescription(SensorEntityDescription): + """Habitipy Sensor Description.""" + + value_path: list[str] + + +SENSOR_DESCRIPTIONS: dict[str, HabitipySensorEntityDescription] = { + "name": HabitipySensorEntityDescription( + key="name", + translation_key="name", + value_path=["profile", "name"], + ), + "hp": HabitipySensorEntityDescription( + key="hp", + translation_key="hp", + native_unit_of_measurement="HP", + suggested_display_precision=0, + value_path=["stats", "hp"], + ), + "maxHealth": HabitipySensorEntityDescription( + key="maxHealth", + translation_key="maxhealth", + native_unit_of_measurement="HP", + value_path=["stats", "maxHealth"], + ), + "mp": HabitipySensorEntityDescription( + key="mp", + translation_key="mp", + native_unit_of_measurement="MP", + suggested_display_precision=0, + value_path=["stats", "mp"], + ), + "maxMP": HabitipySensorEntityDescription( + key="maxMP", + translation_key="maxmp", + native_unit_of_measurement="MP", + value_path=["stats", "maxMP"], + ), + "exp": HabitipySensorEntityDescription( + key="exp", + translation_key="exp", + native_unit_of_measurement="XP", + value_path=["stats", "exp"], + ), + "toNextLevel": HabitipySensorEntityDescription( + key="toNextLevel", + translation_key="tonextlevel", + native_unit_of_measurement="XP", + value_path=["stats", "toNextLevel"], + ), + "lvl": HabitipySensorEntityDescription( + key="lvl", + translation_key="lvl", + native_unit_of_measurement="Lvl", + value_path=["stats", "lvl"], + ), + "gp": HabitipySensorEntityDescription( + key="gp", + translation_key="gp", + native_unit_of_measurement="🜚", # alchemy symbol for gold + suggested_display_precision=2, + value_path=["stats", "gp"], + ), + "class": HabitipySensorEntityDescription( + key="class", + translation_key="class", + value_path=["stats", "class"], + ), +} + TASKS_TYPES = { "habits": SensorType( "Habits", "mdi:clipboard-list-outline", "n_of_tasks", ["habits"] @@ -92,7 +166,8 @@ async def async_setup_entry( await sensor_data.update() entities: list[SensorEntity] = [ - HabitipySensor(name, sensor_type, sensor_data) for sensor_type in SENSORS_TYPES + HabitipySensor(sensor_data, description, config_entry) + for description in SENSOR_DESCRIPTIONS.values() ] entities.extend( HabitipyTaskSensor(name, task_type, sensor_data) for task_type in TASKS_TYPES @@ -153,41 +228,37 @@ class HabitipyData: class HabitipySensor(SensorEntity): """A generic Habitica sensor.""" - def __init__(self, name, sensor_name, updater): + _attr_has_entity_name = True + entity_description: HabitipySensorEntityDescription + + def __init__( + self, + updater, + entity_description: HabitipySensorEntityDescription, + entry: ConfigEntry, + ) -> None: """Initialize a generic Habitica sensor.""" - self._name = name - self._sensor_name = sensor_name - self._sensor_type = SENSORS_TYPES[sensor_name] - self._state = None + super().__init__() + assert entry.unique_id self._updater = updater + self.entity_description = entity_description + self._attr_unique_id = f"{entry.unique_id}_{entity_description.key}" + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + manufacturer=MANUFACTURER, + model=NAME, + name=entry.data[CONF_NAME], + configuration_url=entry.data[CONF_URL], + identifiers={(DOMAIN, entry.unique_id)}, + ) async def async_update(self) -> None: """Update Condition and Forecast.""" await self._updater.update() data = self._updater.data - for element in self._sensor_type.path: + for element in self.entity_description.value_path: data = data[element] - self._state = data - - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return self._sensor_type.icon - - @property - def name(self): - """Return the name of the sensor.""" - return f"{DOMAIN}_{self._name}_{self._sensor_name}" - - @property - def native_value(self): - """Return the state of the device.""" - return self._state - - @property - def native_unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._sensor_type.unit + self._attr_native_value = data class HabitipyTaskSensor(SensorEntity): diff --git a/homeassistant/components/habitica/strings.json b/homeassistant/components/habitica/strings.json index 8dacb0e6321..4d3bee7e726 100644 --- a/homeassistant/components/habitica/strings.json +++ b/homeassistant/components/habitica/strings.json @@ -38,5 +38,45 @@ } } } + }, + "entity": { + "sensor": { + "name": { + "name": "Display name" + }, + "hp": { + "name": "Health" + }, + "maxhealth": { + "name": "Health max." + }, + "mp": { + "name": "Mana" + }, + "maxmp": { + "name": "Mana max." + }, + "exp": { + "name": "Experience" + }, + "tonextlevel": { + "name": "Next Level" + }, + "lvl": { + "name": "Level" + }, + "gp": { + "name": "Gold" + }, + "class": { + "name": "Class", + "state": { + "warrior": "Warrior", + "healer": "Healer", + "wizard": "Mage", + "rogue": "Rogue" + } + } + } } }