Skip to content
Snippets Groups Projects
Unverified Commit 9253fa44 authored by Manu's avatar Manu Committed by GitHub
Browse files

Add binary sensor platform to Habitica integration (#129613)

parent 5f36062e
No related branches found
No related tags found
No related merge requests found
......@@ -30,6 +30,7 @@ CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.CALENDAR,
Platform.SENSOR,
......
"""Binary sensor platform for Habitica integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ASSETS_URL
from .entity import HabiticaBase
from .types import HabiticaConfigEntry
@dataclass(kw_only=True, frozen=True)
class HabiticaBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Habitica Binary Sensor Description."""
value_fn: Callable[[dict[str, Any]], bool | None]
entity_picture: Callable[[dict[str, Any]], str | None]
class HabiticaBinarySensor(StrEnum):
"""Habitica Entities."""
PENDING_QUEST = "pending_quest"
def get_scroll_image_for_pending_quest_invitation(user: dict[str, Any]) -> str | None:
"""Entity picture for pending quest invitation."""
if user["party"]["quest"].get("key") and user["party"]["quest"]["RSVPNeeded"]:
return f"inventory_quest_scroll_{user["party"]["quest"]["key"]}.png"
return None
BINARY_SENSOR_DESCRIPTIONS: tuple[HabiticaBinarySensorEntityDescription, ...] = (
HabiticaBinarySensorEntityDescription(
key=HabiticaBinarySensor.PENDING_QUEST,
translation_key=HabiticaBinarySensor.PENDING_QUEST,
value_fn=lambda user: user["party"]["quest"]["RSVPNeeded"],
entity_picture=get_scroll_image_for_pending_quest_invitation,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: HabiticaConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the habitica binary sensors."""
coordinator = config_entry.runtime_data
async_add_entities(
HabiticaBinarySensorEntity(coordinator, description)
for description in BINARY_SENSOR_DESCRIPTIONS
)
class HabiticaBinarySensorEntity(HabiticaBase, BinarySensorEntity):
"""Representation of a Habitica binary sensor."""
entity_description: HabiticaBinarySensorEntityDescription
@property
def is_on(self) -> bool | None:
"""If the binary sensor is on."""
return self.entity_description.value_fn(self.coordinator.data.user)
@property
def entity_picture(self) -> str | None:
"""Return the entity picture to use in the frontend, if any."""
if entity_picture := self.entity_description.entity_picture(
self.coordinator.data.user
):
return f"{ASSETS_URL}{entity_picture}"
return None
......@@ -135,6 +135,14 @@
"on": "mdi:sleep"
}
}
},
"binary_sensor": {
"pending_quest": {
"default": "mdi:script-outline",
"state": {
"on": "mdi:script-text-outline"
}
}
}
},
"services": {
......
......@@ -38,6 +38,11 @@
}
},
"entity": {
"binary_sensor": {
"pending_quest": {
"name": "Pending quest invitation"
}
},
"button": {
"run_cron": {
"name": "Start my day"
......
{
"data": {
"api_user": "test-api-user",
"profile": { "name": "test-user" },
"stats": {
"buffs": {
"str": 0,
"int": 0,
"per": 0,
"con": 0,
"stealth": 0,
"streaks": false,
"seafoam": false,
"shinySeed": false,
"snowball": false,
"spookySparkles": false
},
"hp": 0,
"mp": 50.89999999999998,
"exp": 737,
"gp": 137.62587214609795,
"lvl": 38,
"class": "wizard",
"maxHealth": 50,
"maxMP": 166,
"toNextLevel": 880,
"points": 5
},
"preferences": {
"sleep": false,
"automaticAllocation": true,
"disableClasses": false
},
"flags": {
"classSelected": true
},
"tasksOrder": {
"rewards": ["5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"],
"todos": [
"88de7cd9-af2b-49ce-9afd-bf941d87336b",
"2f6fcabc-f670-4ec3-ba65-817e8deea490",
"1aa3137e-ef72-4d1f-91ee-41933602f438",
"86ea2475-d1b5-4020-bdcc-c188c7996afa"
],
"dailys": [
"f21fa608-cfc6-4413-9fc7-0eb1b48ca43a",
"bc1d1855-b2b8-4663-98ff-62e7b763dfc4",
"e97659e0-2c42-4599-a7bb-00282adc410d",
"564b9ac9-c53d-4638-9e7f-1cd96fe19baa",
"f2c85972-1a19-4426-bc6d-ce3337b9d99f",
"2c6d136c-a1c3-4bef-b7c4-fa980784b1e1"
],
"habits": ["1d147de6-5c02-4740-8e2f-71d3015a37f4"]
},
"party": {
"quest": {
"RSVPNeeded": false,
"key": null
}
},
"needsCron": true,
"lastCron": "2024-09-21T22:01:55.586Z"
}
}
......@@ -52,6 +52,12 @@
],
"habits": ["1d147de6-5c02-4740-8e2f-71d3015a37f4"]
},
"party": {
"quest": {
"RSVPNeeded": true,
"key": "dustbunnies"
}
},
"needsCron": true,
"lastCron": "2024-09-21T22:01:55.586Z"
}
......
# serializer version: 1
# name: test_binary_sensors[binary_sensor.test_user_pending_quest_invitation-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.test_user_pending_quest_invitation',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Pending quest invitation',
'platform': 'habitica',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <HabiticaBinarySensor.PENDING_QUEST: 'pending_quest'>,
'unique_id': '00000000-0000-0000-0000-000000000000_pending_quest',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.test_user_pending_quest_invitation-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'entity_picture': 'https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_dustbunnies.png',
'friendly_name': 'test-user Pending quest invitation',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.test_user_pending_quest_invitation',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
"""Tests for the Habitica binary sensor platform."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.habitica.const import ASSETS_URL, DEFAULT_URL, DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_OFF, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, load_json_object_fixture, snapshot_platform
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
def binary_sensor_only() -> Generator[None]:
"""Enable only the binarty sensor platform."""
with patch(
"homeassistant.components.habitica.PLATFORMS",
[Platform.BINARY_SENSOR],
):
yield
@pytest.mark.usefixtures("mock_habitica")
async def test_binary_sensors(
hass: HomeAssistant,
config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Test setup of the Habitica binary sensor platform."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.parametrize(
("fixture", "entity_state", "entity_picture"),
[
("user", STATE_ON, f"{ASSETS_URL}inventory_quest_scroll_dustbunnies.png"),
("quest_invitation_off", STATE_OFF, None),
],
)
async def test_pending_quest_states(
hass: HomeAssistant,
config_entry: MockConfigEntry,
aioclient_mock: AiohttpClientMocker,
fixture: str,
entity_state: str,
entity_picture: str | None,
) -> None:
"""Test states of pending quest sensor."""
aioclient_mock.get(
f"{DEFAULT_URL}/api/v3/user",
json=load_json_object_fixture(f"{fixture}.json", DOMAIN),
)
aioclient_mock.get(f"{DEFAULT_URL}/api/v3/tasks/user", json={"data": []})
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert (
state := hass.states.get("binary_sensor.test_user_pending_quest_invitation")
)
assert state.state == entity_state
assert state.attributes.get("entity_picture") == entity_picture
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment