Add entity category and state class to mobile app (#58012)

pull/57919/head
Paulus Schoutsen 2021-10-19 12:29:22 -07:00 committed by GitHub
parent bc9b134c5d
commit ab0247d112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 103 additions and 16 deletions

View File

@ -61,9 +61,11 @@ ERR_INVALID_FORMAT = "invalid_format"
ATTR_SENSOR_ATTRIBUTES = "attributes"
ATTR_SENSOR_DEVICE_CLASS = "device_class"
ATTR_SENSOR_ENTITY_CATEGORY = "entity_category"
ATTR_SENSOR_ICON = "icon"
ATTR_SENSOR_NAME = "name"
ATTR_SENSOR_STATE = "state"
ATTR_SENSOR_STATE_CLASS = "state_class"
ATTR_SENSOR_TYPE = "type"
ATTR_SENSOR_TYPE_BINARY_SENSOR = "binary_sensor"
ATTR_SENSOR_TYPE_SENSOR = "sensor"

View File

@ -9,6 +9,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
from .const import (
ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_ENTITY_CATEGORY,
ATTR_SENSOR_ICON,
ATTR_SENSOR_STATE,
ATTR_SENSOR_TYPE,
@ -86,6 +87,11 @@ class MobileAppEntity(RestoreEntity):
"""Return the icon to use in the frontend, if any."""
return self._config[ATTR_SENSOR_ICON]
@property
def entity_category(self):
"""Return the entity category, if any."""
return self._config.get(ATTR_SENSOR_ENTITY_CATEGORY)
@property
def unique_id(self):
"""Return the unique ID of this sensor."""

View File

@ -1,4 +1,6 @@
"""Sensor platform for mobile_app."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID
from homeassistant.core import callback
@ -12,6 +14,7 @@ from .const import (
ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME,
ATTR_SENSOR_STATE,
ATTR_SENSOR_STATE_CLASS,
ATTR_SENSOR_TYPE,
ATTR_SENSOR_TYPE_SENSOR as ENTITY_TYPE,
ATTR_SENSOR_UNIQUE_ID,
@ -82,3 +85,8 @@ class MobileAppSensor(MobileAppEntity, SensorEntity):
def native_unit_of_measurement(self):
"""Return the unit of measurement this sensor expresses itself in."""
return self._config.get(ATTR_SENSOR_UOM)
@property
def state_class(self) -> str | None:
"""Return state class."""
return self._config.get(ATTR_SENSOR_STATE_CLASS)

View File

@ -22,7 +22,10 @@ from homeassistant.components.device_tracker import (
ATTR_LOCATION_NAME,
)
from homeassistant.components.frontend import MANIFEST_JSON
from homeassistant.components.sensor import DEVICE_CLASSES as SENSOR_CLASSES
from homeassistant.components.sensor import (
DEVICE_CLASSES as SENSOR_CLASSES,
STATE_CLASSES as SENSOSR_STATE_CLASSES,
)
from homeassistant.components.zone.const import DOMAIN as ZONE_DOMAIN
from homeassistant.const import (
ATTR_DEVICE_ID,
@ -41,6 +44,7 @@ from homeassistant.helpers import (
template,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA
from homeassistant.util.decorator import Registry
from .const import (
@ -57,9 +61,11 @@ from .const import (
ATTR_OS_VERSION,
ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_ENTITY_CATEGORY,
ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME,
ATTR_SENSOR_STATE,
ATTR_SENSOR_STATE_CLASS,
ATTR_SENSOR_TYPE,
ATTR_SENSOR_TYPE_BINARY_SENSOR,
ATTR_SENSOR_TYPE_SENSOR,
@ -389,22 +395,38 @@ async def webhook_enable_encryption(hass, config_entry, data):
return json_response({"secret": secret})
def _validate_state_class_sensor(value: dict):
"""Validate we only set state class for sensors."""
if (
ATTR_SENSOR_STATE_CLASS in value
and value[ATTR_SENSOR_TYPE] != ATTR_SENSOR_TYPE_SENSOR
):
raise vol.Invalid("state_class only allowed for sensors")
return value
@WEBHOOK_COMMANDS.register("register_sensor")
@validate_schema(
{
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.All(
vol.Lower, vol.In(COMBINED_CLASSES)
),
vol.Required(ATTR_SENSOR_NAME): cv.string,
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
vol.Optional(ATTR_SENSOR_UOM): cv.string,
vol.Optional(ATTR_SENSOR_STATE, default=None): vol.Any(
None, bool, str, int, float
),
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
}
vol.All(
{
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.All(
vol.Lower, vol.In(COMBINED_CLASSES)
),
vol.Required(ATTR_SENSOR_NAME): cv.string,
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
vol.Optional(ATTR_SENSOR_UOM): cv.string,
vol.Optional(ATTR_SENSOR_STATE, default=None): vol.Any(
None, bool, str, int, float
),
vol.Optional(ATTR_SENSOR_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
vol.Optional(ATTR_SENSOR_STATE_CLASS): vol.In(SENSOSR_STATE_CLASSES),
},
_validate_state_class_sensor,
)
)
async def webhook_register_sensor(hass, config_entry, data):
"""Handle a register sensor webhook."""

View File

@ -1,6 +1,6 @@
"""Entity tests for mobile_app."""
from homeassistant.const import PERCENTAGE, STATE_UNKNOWN
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import device_registry as dr, entity_registry as er
async def test_sensor(hass, create_registrations, webhook_client):
@ -19,7 +19,9 @@ async def test_sensor(hass, create_registrations, webhook_client):
"name": "Battery State",
"state": 100,
"type": "sensor",
"entity_category": "diagnostic",
"unique_id": "battery_state",
"state_class": "total",
"unit_of_measurement": PERCENTAGE,
},
},
@ -38,10 +40,16 @@ async def test_sensor(hass, create_registrations, webhook_client):
assert entity.attributes["icon"] == "mdi:battery"
assert entity.attributes["unit_of_measurement"] == PERCENTAGE
assert entity.attributes["foo"] == "bar"
assert entity.attributes["state_class"] == "total"
assert entity.domain == "sensor"
assert entity.name == "Test 1 Battery State"
assert entity.state == "100"
assert (
er.async_get(hass).async_get("sensor.test_1_battery_state").entity_category
== "diagnostic"
)
update_resp = await webhook_client.post(
webhook_url,
json={

View File

@ -473,3 +473,44 @@ async def test_webhook_handle_scan_tag(hass, create_registrations, webhook_clien
assert len(events) == 1
assert events[0].data["tag_id"] == "mock-tag-id"
assert events[0].data["device_id"] == "mock-device-id"
async def test_register_sensor_limits_state_class(
hass, create_registrations, webhook_client
):
"""Test that we limit state classes to sensors only."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Battery State",
"state": 100,
"type": "sensor",
"state_class": "total",
"unique_id": "abcd",
},
},
)
assert reg_resp.status == 201
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Battery State",
"state": 100,
"type": "binary_sensor",
"state_class": "total",
"unique_id": "efgh",
},
},
)
# This means it was ignored.
assert reg_resp.status == 200