mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 23:57:06 +00:00
Add icons and translations to Habitica (#116204)
* refactor habitica sensors, add strings and icon translations * Change sensor names * remove max_health as it is a fixed value * remove SENSOR_TYPES * removed wrong sensor * Move Data coordinator to separate module * add coordinator.py to coveragerc * add deprecation warning for task sensors * remove unused imports and logger * Revert "add deprecation warning for task sensors" This reverts commit 9e58053f3bb8b34b8e22d525bfd1ff55610f4581. * Update homeassistant/components/habitica/strings.json Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update homeassistant/components/habitica/strings.json Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Revert "Move Data coordinator to separate module" This reverts commit f5c8c3c886a868b2ed50ad2098fe3cb1ccc01c62. * Revert "add coordinator.py to coveragerc" This reverts commit 8ae07a4786db786a73fc527e525813147d1c5ec4. * rename Mana max. to Max. mana * deprecation for yaml import * move SensorType definition before TASK_TYPES * Revert "deprecation for yaml import" This reverts commit 2a1d58ee5ff7d4f1a19b7593cb7f56afde4e1d9d. --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
b426c4133d
commit
0b8838cab8
@ -550,8 +550,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/group/ @home-assistant/core
|
/tests/components/group/ @home-assistant/core
|
||||||
/homeassistant/components/guardian/ @bachya
|
/homeassistant/components/guardian/ @bachya
|
||||||
/tests/components/guardian/ @bachya
|
/tests/components/guardian/ @bachya
|
||||||
/homeassistant/components/habitica/ @ASMfreaK @leikoilja
|
/homeassistant/components/habitica/ @ASMfreaK @leikoilja @tr4nt0r
|
||||||
/tests/components/habitica/ @ASMfreaK @leikoilja
|
/tests/components/habitica/ @ASMfreaK @leikoilja @tr4nt0r
|
||||||
/homeassistant/components/hardkernel/ @home-assistant/core
|
/homeassistant/components/hardkernel/ @home-assistant/core
|
||||||
/tests/components/hardkernel/ @home-assistant/core
|
/tests/components/hardkernel/ @home-assistant/core
|
||||||
/homeassistant/components/hardware/ @home-assistant/core
|
/homeassistant/components/hardware/ @home-assistant/core
|
||||||
|
@ -30,10 +30,11 @@ from .const import (
|
|||||||
EVENT_API_CALL_SUCCESS,
|
EVENT_API_CALL_SUCCESS,
|
||||||
SERVICE_API_CALL,
|
SERVICE_API_CALL,
|
||||||
)
|
)
|
||||||
from .sensor import SENSORS_TYPES
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SENSORS_TYPES = ["name", "hp", "maxHealth", "mp", "maxMP", "exp", "toNextLevel", "lvl"]
|
||||||
|
|
||||||
INSTANCE_SCHEMA = vol.All(
|
INSTANCE_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_SENSORS),
|
cv.deprecated(CONF_SENSORS),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
|
@ -15,3 +15,6 @@ ATTR_ARGS = "args"
|
|||||||
# event constants
|
# event constants
|
||||||
EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success"
|
EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success"
|
||||||
ATTR_DATA = "data"
|
ATTR_DATA = "data"
|
||||||
|
|
||||||
|
MANUFACTURER = "HabitRPG, Inc."
|
||||||
|
NAME = "Habitica"
|
||||||
|
@ -1,4 +1,50 @@
|
|||||||
{
|
{
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"display_name": {
|
||||||
|
"default": "mdi:account-circle"
|
||||||
|
},
|
||||||
|
"health": {
|
||||||
|
"default": "mdi:heart",
|
||||||
|
"state": {
|
||||||
|
"0": "mdi:skull-outline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"health_max": {
|
||||||
|
"default": "mdi:heart"
|
||||||
|
},
|
||||||
|
"mana": {
|
||||||
|
"default": "mdi:flask",
|
||||||
|
"state": {
|
||||||
|
"0": "mdi:flask-empty-outline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mana_max": {
|
||||||
|
"default": "mdi:flask"
|
||||||
|
},
|
||||||
|
"experience": {
|
||||||
|
"default": "mdi:star-four-points"
|
||||||
|
},
|
||||||
|
"experience_max": {
|
||||||
|
"default": "mdi:star-four-points"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"default": "mdi:crown-circle"
|
||||||
|
},
|
||||||
|
"gold": {
|
||||||
|
"default": "mdi:sack"
|
||||||
|
},
|
||||||
|
"class": {
|
||||||
|
"default": "mdi:sword",
|
||||||
|
"state": {
|
||||||
|
"warrior": "mdi:sword",
|
||||||
|
"healer": "mdi:shield",
|
||||||
|
"wizard": "mdi:wizard-hat",
|
||||||
|
"rogue": "mdi:ninja"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"api_call": "mdi:console"
|
"api_call": "mdi:console"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "habitica",
|
"domain": "habitica",
|
||||||
"name": "Habitica",
|
"name": "Habitica",
|
||||||
"codeowners": ["@ASMfreaK", "@leikoilja"],
|
"codeowners": ["@ASMfreaK", "@leikoilja", "@tr4nt0r"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/habitica",
|
"documentation": "https://www.home-assistant.io/integrations/habitica",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
|
@ -3,42 +3,123 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from enum import StrEnum
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import logging
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
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.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, MANUFACTURER, NAME
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
||||||
|
|
||||||
SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"])
|
|
||||||
|
|
||||||
SENSORS_TYPES = {
|
@dataclass(kw_only=True, frozen=True)
|
||||||
"name": SensorType("Name", None, None, ["profile", "name"]),
|
class HabitipySensorEntityDescription(SensorEntityDescription):
|
||||||
"hp": SensorType("HP", "mdi:heart", "HP", ["stats", "hp"]),
|
"""Habitipy Sensor Description."""
|
||||||
"maxHealth": SensorType("max HP", "mdi:heart", "HP", ["stats", "maxHealth"]),
|
|
||||||
"mp": SensorType("Mana", "mdi:auto-fix", "MP", ["stats", "mp"]),
|
value_path: list[str]
|
||||||
"maxMP": SensorType("max Mana", "mdi:auto-fix", "MP", ["stats", "maxMP"]),
|
|
||||||
"exp": SensorType("EXP", "mdi:star", "EXP", ["stats", "exp"]),
|
|
||||||
"toNextLevel": SensorType("Next Lvl", "mdi:star", "EXP", ["stats", "toNextLevel"]),
|
class HabitipySensorEntity(StrEnum):
|
||||||
"lvl": SensorType(
|
"""Habitipy Entities."""
|
||||||
"Lvl", "mdi:arrow-up-bold-circle-outline", "Lvl", ["stats", "lvl"]
|
|
||||||
|
DISPLAY_NAME = "display_name"
|
||||||
|
HEALTH = "health"
|
||||||
|
HEALTH_MAX = "health_max"
|
||||||
|
MANA = "mana"
|
||||||
|
MANA_MAX = "mana_max"
|
||||||
|
EXPERIENCE = "experience"
|
||||||
|
EXPERIENCE_MAX = "experience_max"
|
||||||
|
LEVEL = "level"
|
||||||
|
GOLD = "gold"
|
||||||
|
CLASS = "class"
|
||||||
|
|
||||||
|
|
||||||
|
SENSOR_DESCRIPTIONS: dict[str, HabitipySensorEntityDescription] = {
|
||||||
|
HabitipySensorEntity.DISPLAY_NAME: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.DISPLAY_NAME,
|
||||||
|
translation_key=HabitipySensorEntity.DISPLAY_NAME,
|
||||||
|
value_path=["profile", "name"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.HEALTH: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.HEALTH,
|
||||||
|
translation_key=HabitipySensorEntity.HEALTH,
|
||||||
|
native_unit_of_measurement="HP",
|
||||||
|
suggested_display_precision=0,
|
||||||
|
value_path=["stats", "hp"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.HEALTH_MAX: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.HEALTH_MAX,
|
||||||
|
translation_key=HabitipySensorEntity.HEALTH_MAX,
|
||||||
|
native_unit_of_measurement="HP",
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
value_path=["stats", "maxHealth"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.MANA: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.MANA,
|
||||||
|
translation_key=HabitipySensorEntity.MANA,
|
||||||
|
native_unit_of_measurement="MP",
|
||||||
|
suggested_display_precision=0,
|
||||||
|
value_path=["stats", "mp"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.MANA_MAX: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.MANA_MAX,
|
||||||
|
translation_key=HabitipySensorEntity.MANA_MAX,
|
||||||
|
native_unit_of_measurement="MP",
|
||||||
|
value_path=["stats", "maxMP"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.EXPERIENCE: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.EXPERIENCE,
|
||||||
|
translation_key=HabitipySensorEntity.EXPERIENCE,
|
||||||
|
native_unit_of_measurement="XP",
|
||||||
|
value_path=["stats", "exp"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.EXPERIENCE_MAX: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.EXPERIENCE_MAX,
|
||||||
|
translation_key=HabitipySensorEntity.EXPERIENCE_MAX,
|
||||||
|
native_unit_of_measurement="XP",
|
||||||
|
value_path=["stats", "toNextLevel"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.LEVEL: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.LEVEL,
|
||||||
|
translation_key=HabitipySensorEntity.LEVEL,
|
||||||
|
value_path=["stats", "lvl"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.GOLD: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.GOLD,
|
||||||
|
translation_key=HabitipySensorEntity.GOLD,
|
||||||
|
native_unit_of_measurement="GP",
|
||||||
|
suggested_display_precision=2,
|
||||||
|
value_path=["stats", "gp"],
|
||||||
|
),
|
||||||
|
HabitipySensorEntity.CLASS: HabitipySensorEntityDescription(
|
||||||
|
key=HabitipySensorEntity.CLASS,
|
||||||
|
translation_key=HabitipySensorEntity.CLASS,
|
||||||
|
value_path=["stats", "class"],
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=["warrior", "healer", "wizard", "rogue"],
|
||||||
),
|
),
|
||||||
"gp": SensorType("Gold", "mdi:circle-multiple", "Gold", ["stats", "gp"]),
|
|
||||||
"class": SensorType("Class", "mdi:sword", None, ["stats", "class"]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"])
|
||||||
TASKS_TYPES = {
|
TASKS_TYPES = {
|
||||||
"habits": SensorType(
|
"habits": SensorType(
|
||||||
"Habits", "mdi:clipboard-list-outline", "n_of_tasks", ["habits"]
|
"Habits", "mdi:clipboard-list-outline", "n_of_tasks", ["habits"]
|
||||||
@ -92,10 +173,12 @@ async def async_setup_entry(
|
|||||||
await sensor_data.update()
|
await sensor_data.update()
|
||||||
|
|
||||||
entities: list[SensorEntity] = [
|
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(
|
entities.extend(
|
||||||
HabitipyTaskSensor(name, task_type, sensor_data) for task_type in TASKS_TYPES
|
HabitipyTaskSensor(name, task_type, sensor_data, config_entry)
|
||||||
|
for task_type in TASKS_TYPES
|
||||||
)
|
)
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
@ -103,7 +186,9 @@ async def async_setup_entry(
|
|||||||
class HabitipyData:
|
class HabitipyData:
|
||||||
"""Habitica API user data cache."""
|
"""Habitica API user data cache."""
|
||||||
|
|
||||||
def __init__(self, api):
|
tasks: dict[str, Any]
|
||||||
|
|
||||||
|
def __init__(self, api) -> None:
|
||||||
"""Habitica API user data cache."""
|
"""Habitica API user data cache."""
|
||||||
self.api = api
|
self.api = api
|
||||||
self.data = None
|
self.data = None
|
||||||
@ -153,53 +238,59 @@ class HabitipyData:
|
|||||||
class HabitipySensor(SensorEntity):
|
class HabitipySensor(SensorEntity):
|
||||||
"""A generic Habitica sensor."""
|
"""A generic Habitica sensor."""
|
||||||
|
|
||||||
def __init__(self, name, sensor_name, updater):
|
_attr_has_entity_name = True
|
||||||
|
entity_description: HabitipySensorEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator,
|
||||||
|
entity_description: HabitipySensorEntityDescription,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
) -> None:
|
||||||
"""Initialize a generic Habitica sensor."""
|
"""Initialize a generic Habitica sensor."""
|
||||||
self._name = name
|
super().__init__()
|
||||||
self._sensor_name = sensor_name
|
if TYPE_CHECKING:
|
||||||
self._sensor_type = SENSORS_TYPES[sensor_name]
|
assert entry.unique_id
|
||||||
self._state = None
|
self.coordinator = coordinator
|
||||||
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:
|
async def async_update(self) -> None:
|
||||||
"""Update Condition and Forecast."""
|
"""Update Sensor state."""
|
||||||
await self._updater.update()
|
await self.coordinator.update()
|
||||||
data = self._updater.data
|
data = self.coordinator.data
|
||||||
for element in self._sensor_type.path:
|
for element in self.entity_description.value_path:
|
||||||
data = data[element]
|
data = data[element]
|
||||||
self._state = data
|
self._attr_native_value = 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
|
|
||||||
|
|
||||||
|
|
||||||
class HabitipyTaskSensor(SensorEntity):
|
class HabitipyTaskSensor(SensorEntity):
|
||||||
"""A Habitica task sensor."""
|
"""A Habitica task sensor."""
|
||||||
|
|
||||||
def __init__(self, name, task_name, updater):
|
def __init__(self, name, task_name, updater, entry):
|
||||||
"""Initialize a generic Habitica task."""
|
"""Initialize a generic Habitica task."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._task_name = task_name
|
self._task_name = task_name
|
||||||
self._task_type = TASKS_TYPES[task_name]
|
self._task_type = TASKS_TYPES[task_name]
|
||||||
self._state = None
|
self._state = None
|
||||||
self._updater = updater
|
self._updater = updater
|
||||||
|
self._attr_unique_id = f"{entry.unique_id}_{task_name}"
|
||||||
|
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:
|
async def async_update(self) -> None:
|
||||||
"""Update Condition and Forecast."""
|
"""Update Condition and Forecast."""
|
||||||
|
@ -19,6 +19,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"display_name": {
|
||||||
|
"name": "Display name"
|
||||||
|
},
|
||||||
|
"health": {
|
||||||
|
"name": "Health"
|
||||||
|
},
|
||||||
|
"health_max": {
|
||||||
|
"name": "Max. health"
|
||||||
|
},
|
||||||
|
"mana": {
|
||||||
|
"name": "Mana"
|
||||||
|
},
|
||||||
|
"mana_max": {
|
||||||
|
"name": "Max. mana"
|
||||||
|
},
|
||||||
|
"experience": {
|
||||||
|
"name": "Experience"
|
||||||
|
},
|
||||||
|
"experience_max": {
|
||||||
|
"name": "Next level"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"name": "Level"
|
||||||
|
},
|
||||||
|
"gold": {
|
||||||
|
"name": "Gold"
|
||||||
|
},
|
||||||
|
"class": {
|
||||||
|
"name": "Class",
|
||||||
|
"state": {
|
||||||
|
"warrior": "Warrior",
|
||||||
|
"healer": "Healer",
|
||||||
|
"wizard": "Mage",
|
||||||
|
"rogue": "Rogue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"api_call": {
|
"api_call": {
|
||||||
"name": "API name",
|
"name": "API name",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user