Fix some mobile app sensor registration/update issues (#86965)

Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
Paulus Schoutsen 2023-01-30 14:08:19 -05:00 committed by GitHub
parent f7fdaadde0
commit e0f8b5bbd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 21 deletions

View file

@ -1,6 +1,7 @@
"""Sensor platform for mobile_app."""
from __future__ import annotations
from datetime import date, datetime
from typing import Any
from homeassistant.components.sensor import RestoreSensor, SensorDeviceClass
@ -10,6 +11,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util
from .const import (
@ -99,7 +101,7 @@ class MobileAppSensor(MobileAppEntity, RestoreSensor):
self._config[ATTR_SENSOR_UOM] = last_sensor_data.native_unit_of_measurement
@property
def native_value(self):
def native_value(self) -> StateType | date | datetime:
"""Return the state of the sensor."""
if (state := self._config[ATTR_SENSOR_STATE]) in (None, STATE_UNKNOWN):
return None
@ -122,7 +124,7 @@ class MobileAppSensor(MobileAppEntity, RestoreSensor):
return state
@property
def native_unit_of_measurement(self):
def native_unit_of_measurement(self) -> str | None:
"""Return the unit of measurement this sensor expresses itself in."""
return self._config.get(ATTR_SENSOR_UOM)

View file

@ -22,9 +22,7 @@ from homeassistant.components import (
notify as hass_notify,
tag,
)
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES as BINARY_SENSOR_CLASSES,
)
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.camera import CameraEntityFeature
from homeassistant.components.device_tracker import (
ATTR_BATTERY,
@ -33,10 +31,7 @@ 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,
STATE_CLASSES as SENSOSR_STATE_CLASSES,
)
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@ -58,7 +53,7 @@ from homeassistant.helpers import (
template,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA
from homeassistant.helpers.entity import EntityCategory
from homeassistant.util.decorator import Registry
from .const import (
@ -131,8 +126,7 @@ WEBHOOK_COMMANDS: Registry[
str, Callable[[HomeAssistant, ConfigEntry, Any], Coroutine[Any, Any, Response]]
] = Registry()
COMBINED_CLASSES = set(BINARY_SENSOR_CLASSES + SENSOR_CLASSES)
SENSOR_TYPES = [ATTR_SENSOR_TYPE_BINARY_SENSOR, ATTR_SENSOR_TYPE_SENSOR]
SENSOR_TYPES = (ATTR_SENSOR_TYPE_BINARY_SENSOR, ATTR_SENSOR_TYPE_SENSOR)
WEBHOOK_PAYLOAD_SCHEMA = vol.Schema(
{
@ -507,19 +501,27 @@ def _extract_sensor_unique_id(webhook_id: str, unique_id: str) -> str:
vol.All(
{
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.All(
vol.Lower, vol.In(COMBINED_CLASSES)
vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.Any(
None,
vol.All(vol.Lower, vol.Coerce(BinarySensorDeviceClass)),
vol.All(vol.Lower, vol.Coerce(SensorDeviceClass)),
),
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_UOM): vol.Any(None, cv.string),
vol.Optional(ATTR_SENSOR_STATE, default=None): vol.Any(
None, bool, str, int, float
None, bool, int, float, str
),
vol.Optional(ATTR_SENSOR_ENTITY_CATEGORY): vol.Any(
None, vol.Coerce(EntityCategory)
),
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): vol.Any(
None, cv.icon
),
vol.Optional(ATTR_SENSOR_STATE_CLASS): vol.Any(
None, vol.Coerce(SensorStateClass)
),
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),
vol.Optional(ATTR_SENSOR_DISABLED): bool,
},
_validate_state_class_sensor,
@ -619,8 +621,10 @@ async def webhook_update_sensor_states(
sensor_schema_full = vol.Schema(
{
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float),
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): vol.Any(
None, cv.icon
),
vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, int, float, str),
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
}

View file

@ -979,6 +979,32 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client):
entry = ent_reg.async_get("sensor.test_1_battery_state")
assert entry.disabled_by is None
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "New Name 2",
"state": 100,
"type": "sensor",
"unique_id": "abcd",
"state_class": None,
"device_class": None,
"entity_category": None,
"icon": None,
"unit_of_measurement": None,
},
},
)
assert reg_resp.status == HTTPStatus.CREATED
entry = ent_reg.async_get("sensor.test_1_battery_state")
assert entry.original_name == "Test 1 New Name 2"
assert entry.device_class is None
assert entry.unit_of_measurement is None
assert entry.entity_category is None
assert entry.original_icon is None
async def test_webhook_handle_conversation_process(
hass, create_registrations, webhook_client, mock_agent
@ -1017,3 +1043,57 @@ async def test_webhook_handle_conversation_process(
},
"conversation_id": None,
}
async def test_sending_sensor_state(hass, create_registrations, webhook_client, caplog):
"""Test that we can register and send sensor state as number and None."""
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",
"unique_id": "abcd",
},
},
)
assert reg_resp.status == HTTPStatus.CREATED
ent_reg = er.async_get(hass)
entry = ent_reg.async_get("sensor.test_1_battery_state")
assert entry.original_name == "Test 1 Battery State"
assert entry.device_class is None
assert entry.unit_of_measurement is None
assert entry.entity_category is None
assert entry.original_icon == "mdi:cellphone"
assert entry.disabled_by is None
await hass.async_block_till_done()
state = hass.states.get("sensor.test_1_battery_state")
assert state is not None
assert state.state == "100"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "update_sensor_states",
"data": {
"state": 50.0000,
"type": "sensor",
"unique_id": "abcd",
},
},
)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_1_battery_state")
assert state is not None
assert state.state == "50.0"