mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Fix some mobile app sensor registration/update issues (#86965)
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
f7fdaadde0
commit
e0f8b5bbd1
@ -1,6 +1,7 @@
|
|||||||
"""Sensor platform for mobile_app."""
|
"""Sensor platform for mobile_app."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import date, datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.sensor import RestoreSensor, SensorDeviceClass
|
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 import entity_registry as er
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -99,7 +101,7 @@ class MobileAppSensor(MobileAppEntity, RestoreSensor):
|
|||||||
self._config[ATTR_SENSOR_UOM] = last_sensor_data.native_unit_of_measurement
|
self._config[ATTR_SENSOR_UOM] = last_sensor_data.native_unit_of_measurement
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self):
|
def native_value(self) -> StateType | date | datetime:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
if (state := self._config[ATTR_SENSOR_STATE]) in (None, STATE_UNKNOWN):
|
if (state := self._config[ATTR_SENSOR_STATE]) in (None, STATE_UNKNOWN):
|
||||||
return None
|
return None
|
||||||
@ -122,7 +124,7 @@ class MobileAppSensor(MobileAppEntity, RestoreSensor):
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
@property
|
@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 the unit of measurement this sensor expresses itself in."""
|
||||||
return self._config.get(ATTR_SENSOR_UOM)
|
return self._config.get(ATTR_SENSOR_UOM)
|
||||||
|
|
||||||
|
@ -22,9 +22,7 @@ from homeassistant.components import (
|
|||||||
notify as hass_notify,
|
notify as hass_notify,
|
||||||
tag,
|
tag,
|
||||||
)
|
)
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
DEVICE_CLASSES as BINARY_SENSOR_CLASSES,
|
|
||||||
)
|
|
||||||
from homeassistant.components.camera import CameraEntityFeature
|
from homeassistant.components.camera import CameraEntityFeature
|
||||||
from homeassistant.components.device_tracker import (
|
from homeassistant.components.device_tracker import (
|
||||||
ATTR_BATTERY,
|
ATTR_BATTERY,
|
||||||
@ -33,10 +31,7 @@ from homeassistant.components.device_tracker import (
|
|||||||
ATTR_LOCATION_NAME,
|
ATTR_LOCATION_NAME,
|
||||||
)
|
)
|
||||||
from homeassistant.components.frontend import MANIFEST_JSON
|
from homeassistant.components.frontend import MANIFEST_JSON
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||||
DEVICE_CLASSES as SENSOR_CLASSES,
|
|
||||||
STATE_CLASSES as SENSOSR_STATE_CLASSES,
|
|
||||||
)
|
|
||||||
from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN
|
from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -58,7 +53,7 @@ from homeassistant.helpers import (
|
|||||||
template,
|
template,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
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 homeassistant.util.decorator import Registry
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -131,8 +126,7 @@ WEBHOOK_COMMANDS: Registry[
|
|||||||
str, Callable[[HomeAssistant, ConfigEntry, Any], Coroutine[Any, Any, Response]]
|
str, Callable[[HomeAssistant, ConfigEntry, Any], Coroutine[Any, Any, Response]]
|
||||||
] = Registry()
|
] = 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(
|
WEBHOOK_PAYLOAD_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -507,19 +501,27 @@ def _extract_sensor_unique_id(webhook_id: str, unique_id: str) -> str:
|
|||||||
vol.All(
|
vol.All(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
|
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
|
||||||
vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.All(
|
vol.Optional(ATTR_SENSOR_DEVICE_CLASS): vol.Any(
|
||||||
vol.Lower, vol.In(COMBINED_CLASSES)
|
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_NAME): cv.string,
|
||||||
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
|
vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES),
|
||||||
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
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(
|
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,
|
vol.Optional(ATTR_SENSOR_DISABLED): bool,
|
||||||
},
|
},
|
||||||
_validate_state_class_sensor,
|
_validate_state_class_sensor,
|
||||||
@ -619,8 +621,10 @@ async def webhook_update_sensor_states(
|
|||||||
sensor_schema_full = vol.Schema(
|
sensor_schema_full = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
|
vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict,
|
||||||
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
|
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): vol.Any(
|
||||||
vol.Required(ATTR_SENSOR_STATE): vol.Any(None, bool, str, int, float),
|
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_TYPE): vol.In(SENSOR_TYPES),
|
||||||
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string,
|
||||||
}
|
}
|
||||||
|
@ -979,6 +979,32 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client):
|
|||||||
entry = ent_reg.async_get("sensor.test_1_battery_state")
|
entry = ent_reg.async_get("sensor.test_1_battery_state")
|
||||||
assert entry.disabled_by is None
|
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(
|
async def test_webhook_handle_conversation_process(
|
||||||
hass, create_registrations, webhook_client, mock_agent
|
hass, create_registrations, webhook_client, mock_agent
|
||||||
@ -1017,3 +1043,57 @@ async def test_webhook_handle_conversation_process(
|
|||||||
},
|
},
|
||||||
"conversation_id": None,
|
"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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user