diff --git a/homeassistant/core.py b/homeassistant/core.py index 9c5d32934a8..d7535907dfc 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -72,6 +72,7 @@ from .const import ( MAX_EXPECTED_ENTITY_IDS, MAX_LENGTH_EVENT_EVENT_TYPE, MAX_LENGTH_STATE_STATE, + STATE_UNKNOWN, __version__, ) from .exceptions import ( @@ -1794,18 +1795,13 @@ class State: ) -> None: """Initialize a new state.""" self._cache: dict[str, Any] = {} - state = str(state) - if validate_entity_id and not valid_entity_id(entity_id): raise InvalidEntityFormatError( f"Invalid entity id encountered: {entity_id}. " "Format should be ." ) - - validate_state(state) - self.entity_id = entity_id - self.state = state + self.state = state if type(state) is str else str(state) # State only creates and expects a ReadOnlyDict so # there is no need to check for subclassing with # isinstance here so we can use the faster type check. @@ -2270,9 +2266,11 @@ class StateMachine: This method must be run in the event loop. """ + state = str(new_state) + validate_state(state) self.async_set_internal( entity_id.lower(), - str(new_state), + state, attributes or {}, force_update, context, @@ -2298,6 +2296,8 @@ class StateMachine: breaking changes to this function in the future and it should not be used in integrations. + Callers are responsible for ensuring the entity_id is lower case. + This method must be run in the event loop. """ # Most cases the key will be in the dict @@ -2356,6 +2356,16 @@ class StateMachine: assert old_state is not None attributes = old_state.attributes + if not same_state and len(new_state) > MAX_LENGTH_STATE_STATE: + _LOGGER.error( + "State %s for %s is longer than %s, falling back to %s", + new_state, + entity_id, + MAX_LENGTH_STATE_STATE, + STATE_UNKNOWN, + ) + new_state = STATE_UNKNOWN + # This is intentionally called with positional only arguments for performance # reasons state = State( diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 52aac7cca79..a3edf6bb64f 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -31,7 +31,6 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, - MAX_LENGTH_STATE_STATE, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, @@ -1217,16 +1216,6 @@ class Entity( self._context = None self._context_set = None - if len(state) > MAX_LENGTH_STATE_STATE: - _LOGGER.error( - "State %s for %s is longer than %s, falling back to %s", - state, - self.entity_id, - MAX_LENGTH_STATE_STATE, - STATE_UNKNOWN, - ) - state = STATE_UNKNOWN - # Intentionally called with positional args for performance reasons self.hass.states.async_set_internal( self.entity_id, diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 04159a91d6b..137b2a7e8a7 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -1711,7 +1711,7 @@ async def test_invalid_state( ent.async_write_ha_state() assert hass.states.get("test.test").state == STATE_UNKNOWN assert ( - "homeassistant.helpers.entity", + "homeassistant.core", logging.ERROR, f"State {long_state} for test.test is longer than 255, " f"falling back to {STATE_UNKNOWN}", diff --git a/tests/test_core.py b/tests/test_core.py index ceab3ce327c..50f7f92727b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -35,6 +35,7 @@ from homeassistant.const import ( EVENT_STATE_CHANGED, EVENT_STATE_REPORTED, MATCH_ALL, + STATE_UNKNOWN, ) from homeassistant.core import ( CoreState, @@ -1368,9 +1369,6 @@ def test_state_init() -> None: with pytest.raises(InvalidEntityFormatError): ha.State("invalid_entity_format", "test_state") - with pytest.raises(InvalidStateError): - ha.State("domain.long_state", "t" * 256) - def test_state_domain() -> None: """Test domain.""" @@ -1440,6 +1438,38 @@ def test_state_repr() -> None: ) +async def test_statemachine_async_set_invalid_state(hass: HomeAssistant) -> None: + """Test setting an invalid state with the async_set method.""" + with pytest.raises( + InvalidStateError, + match="Invalid state with length 256. State max length is 255 characters.", + ): + hass.states.async_set("light.bowl", "o" * 256, {}) + + +async def test_statemachine_async_set_internal_invalid_state( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test setting an invalid state with the async_set_internal method.""" + long_state = "o" * 256 + hass.states.async_set_internal( + "light.bowl", + long_state, + {}, + force_update=False, + context=None, + state_info=None, + timestamp=time.time(), + ) + assert hass.states.get("light.bowl").state == STATE_UNKNOWN + assert ( + "homeassistant.core", + logging.ERROR, + f"State {long_state} for light.bowl is longer than 255, " + f"falling back to {STATE_UNKNOWN}", + ) in caplog.record_tuples + + async def test_statemachine_is_state(hass: HomeAssistant) -> None: """Test is_state method.""" hass.states.async_set("light.bowl", "on", {})