Allow binary sensor state to be None (#60193)

This commit is contained in:
Franck Nijhof 2021-12-22 12:24:29 +01:00 committed by GitHub
parent 4805b67300
commit 60b2cdd069
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 75 additions and 58 deletions

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import final
from typing import Literal, final
import voluptuous as vol
@ -18,7 +18,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType, StateType
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
@ -200,6 +200,8 @@ class BinarySensorEntity(Entity):
@final
@property
def state(self) -> StateType:
def state(self) -> Literal["on", "off"] | None:
"""Return the state of the binary sensor."""
return STATE_ON if self.is_on else STATE_OFF
if (is_on := self.is_on) is None:
return None
return STATE_ON if is_on else STATE_OFF

View File

@ -8,7 +8,7 @@ from homeassistant.const import STATE_OFF, STATE_ON
def test_state():
"""Test binary sensor state."""
sensor = binary_sensor.BinarySensorEntity()
assert sensor.state == STATE_OFF
assert sensor.state is None
with mock.patch(
"homeassistant.components.binary_sensor.BinarySensorEntity.is_on",
new=False,

View File

@ -1,7 +1,13 @@
"""The tests for the Group Binary Sensor platform."""
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.group import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
from homeassistant.const import (
ATTR_ENTITY_ID,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
@ -65,7 +71,7 @@ async def test_state_reporting_all(hass):
hass.states.async_set("binary_sensor.test1", STATE_ON)
hass.states.async_set("binary_sensor.test2", STATE_UNAVAILABLE)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_OFF
assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_UNKNOWN
hass.states.async_set("binary_sensor.test1", STATE_ON)
hass.states.async_set("binary_sensor.test2", STATE_OFF)
@ -114,7 +120,7 @@ async def test_state_reporting_any(hass):
hass.states.async_set("binary_sensor.test1", STATE_ON)
hass.states.async_set("binary_sensor.test2", STATE_UNAVAILABLE)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_OFF
assert hass.states.get("binary_sensor.binary_sensor_group").state == STATE_UNKNOWN
hass.states.async_set("binary_sensor.test1", STATE_ON)
hass.states.async_set("binary_sensor.test2", STATE_OFF)

View File

@ -22,7 +22,7 @@ from homeassistant.components.homematicip_cloud.generic_entity import (
ATTR_RSSI_DEVICE,
ATTR_SABOTAGE,
)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.setup import async_setup_component
from .helper import async_manipulate_test_data, get_and_check_entity_basics
@ -152,7 +152,7 @@ async def test_hmip_contact_interface(hass, default_mock_hap_factory):
await async_manipulate_test_data(hass, hmip_device, "windowState", None)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
assert ha_state.state == STATE_UNKNOWN
async def test_hmip_shutter_contact(hass, default_mock_hap_factory):
@ -185,7 +185,7 @@ async def test_hmip_shutter_contact(hass, default_mock_hap_factory):
await async_manipulate_test_data(hass, hmip_device, "windowState", None)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
assert ha_state.state == STATE_UNKNOWN
# test common attributes
assert ha_state.attributes[ATTR_RSSI_DEVICE] == -54
@ -215,7 +215,7 @@ async def test_hmip_shutter_contact_optical(hass, default_mock_hap_factory):
await async_manipulate_test_data(hass, hmip_device, "windowState", None)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
assert ha_state.state == STATE_UNKNOWN
# test common attributes
assert ha_state.attributes[ATTR_RSSI_DEVICE] == -72
@ -562,7 +562,7 @@ async def test_hmip_multi_contact_interface(hass, default_mock_hap_factory):
await async_manipulate_test_data(hass, hmip_device, "windowState", None, channel=5)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
assert ha_state.state == STATE_UNKNOWN
ha_state, hmip_device = get_and_check_entity_basics(
hass,
@ -572,4 +572,4 @@ async def test_hmip_multi_contact_interface(hass, default_mock_hap_factory):
"HmIP-FCI6",
)
assert ha_state.state == STATE_OFF
assert ha_state.state == STATE_UNKNOWN

View File

@ -1,7 +1,7 @@
"""Entity tests for mobile_app."""
from http import HTTPStatus
from homeassistant.const import STATE_OFF
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers import device_registry as dr
@ -198,7 +198,7 @@ async def test_register_sensor_no_state(hass, create_registrations, webhook_clie
assert entity.domain == "binary_sensor"
assert entity.name == "Test 1 Is Charging"
assert entity.state == STATE_OFF # Binary sensor defaults to off
assert entity.state == STATE_UNKNOWN
reg_resp = await webhook_client.post(
webhook_url,
@ -223,7 +223,7 @@ async def test_register_sensor_no_state(hass, create_registrations, webhook_clie
assert entity.domain == "binary_sensor"
assert entity.name == "Test 1 Backup Is Charging"
assert entity.state == STATE_OFF # Binary sensor defaults to off
assert entity.state == STATE_UNKNOWN
async def test_update_sensor_no_state(hass, create_registrations, webhook_client):
@ -270,4 +270,4 @@ async def test_update_sensor_no_state(hass, create_registrations, webhook_client
assert json == {"is_charging": {"success": True}}
updated_entity = hass.states.get("binary_sensor.test_1_is_charging")
assert updated_entity.state == STATE_OFF # Binary sensor defaults to off
assert updated_entity.state == STATE_UNKNOWN

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import State
@ -141,7 +142,7 @@ async def test_all_binary_sensor(hass, expected, mock_do_cycle):
(
[0x00],
True,
STATE_OFF,
STATE_UNKNOWN,
STATE_UNAVAILABLE,
),
],

View File

@ -12,6 +12,7 @@ from homeassistant.const import (
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
import homeassistant.core as ha
from homeassistant.setup import async_setup_component
@ -256,7 +257,7 @@ async def test_setting_sensor_value_via_mqtt_message(hass, mqtt_mock):
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
async_fire_mqtt_message(hass, "test-topic", "ON")
state = hass.states.get("binary_sensor.test")
@ -286,11 +287,11 @@ async def test_invalid_sensor_value_via_mqtt_message(hass, mqtt_mock, caplog):
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
async_fire_mqtt_message(hass, "test-topic", "0N")
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
assert "No matching payload found for entity" in caplog.text
caplog.clear()
assert "No matching payload found for entity" not in caplog.text
@ -325,7 +326,7 @@ async def test_setting_sensor_value_via_mqtt_message_and_template(hass, mqtt_moc
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
async_fire_mqtt_message(hass, "test-topic", "")
state = hass.states.get("binary_sensor.test")
@ -357,7 +358,7 @@ async def test_setting_sensor_value_via_mqtt_message_and_template2(
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
async_fire_mqtt_message(hass, "test-topic", "on")
state = hass.states.get("binary_sensor.test")
@ -395,7 +396,7 @@ async def test_setting_sensor_value_via_mqtt_message_and_template_and_raw_state_
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
async_fire_mqtt_message(hass, "test-topic", b"\x01")
state = hass.states.get("binary_sensor.test")
@ -427,11 +428,11 @@ async def test_setting_sensor_value_via_mqtt_message_empty_template(
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
async_fire_mqtt_message(hass, "test-topic", "DEF")
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
assert "Empty template output" in caplog.text
async_fire_mqtt_message(hass, "test-topic", "ABC")

View File

@ -13,6 +13,7 @@ from homeassistant.const import (
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
import homeassistant.core as ha
import homeassistant.util.dt as dt_util
@ -53,7 +54,7 @@ async def test_default_setup(hass, monkeypatch):
# test default state of sensor loaded from config
config_sensor = hass.states.get("binary_sensor.test")
assert config_sensor
assert config_sensor.state == STATE_OFF
assert config_sensor.state == STATE_UNKNOWN
assert config_sensor.attributes["device_class"] == "door"
# test on event for config sensor
@ -95,7 +96,7 @@ async def test_entity_availability(hass, monkeypatch):
)
# Entities are available by default
assert hass.states.get("binary_sensor.test").state == STATE_OFF
assert hass.states.get("binary_sensor.test").state == STATE_UNKNOWN
# Mock a disconnect of the Rflink device
disconnect_callback()
@ -113,7 +114,7 @@ async def test_entity_availability(hass, monkeypatch):
await hass.async_block_till_done()
# Entities should be available again
assert hass.states.get("binary_sensor.test").state == STATE_OFF
assert hass.states.get("binary_sensor.test").state == STATE_UNKNOWN
async def test_off_delay(hass, legacy_patchable_time, monkeypatch):

View File

@ -3,6 +3,7 @@ import pytest
from homeassistant.components.rfxtrx import DOMAIN
from homeassistant.components.rfxtrx.const import ATTR_EVENT
from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import State
from tests.common import MockConfigEntry, mock_restore_cache
@ -32,7 +33,7 @@ async def test_one(hass, rfxtrx):
state = hass.states.get("binary_sensor.ac_213c7f2_48")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "AC 213c7f2:48"
@ -57,7 +58,7 @@ async def test_one_pt2262(hass, rfxtrx):
state = hass.states.get("binary_sensor.pt2262_22670e")
assert state
assert state.state == "off" # probably aught to be unknown
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "PT2262 22670e"
await rfxtrx.signal("0913000022670e013970")
@ -84,12 +85,12 @@ async def test_pt2262_unconfigured(hass, rfxtrx):
state = hass.states.get("binary_sensor.pt2262_22670e")
assert state
assert state.state == "off" # probably aught to be unknown
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "PT2262 22670e"
state = hass.states.get("binary_sensor.pt2262_226707")
assert state
assert state.state == "off" # probably aught to be unknown
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "PT2262 226707"
@ -133,17 +134,17 @@ async def test_several(hass, rfxtrx):
state = hass.states.get("binary_sensor.ac_213c7f2_48")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "AC 213c7f2:48"
state = hass.states.get("binary_sensor.ac_118cdea_2")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "AC 118cdea:2"
state = hass.states.get("binary_sensor.ac_118cdea_3")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "AC 118cdea:3"
# "2: Group on"
@ -214,7 +215,7 @@ async def test_off_delay(hass, rfxtrx, timestep):
state = hass.states.get("binary_sensor.ac_118cdea_2")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
await rfxtrx.signal("0b1100100118cdea02010f70")
state = hass.states.get("binary_sensor.ac_118cdea_2")
@ -317,5 +318,5 @@ async def test_pt2262_duplicate_id(hass, rfxtrx):
state = hass.states.get("binary_sensor.pt2262_22670e")
assert state
assert state.state == "off" # probably aught to be unknown
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "PT2262 22670e"

View File

@ -6,6 +6,7 @@ import serial.tools.list_ports
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.rfxtrx import DOMAIN, config_flow
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers import device_registry as dr, entity_registry as er
from tests.common import MockConfigEntry
@ -367,7 +368,7 @@ async def test_options_add_device(hass):
state = hass.states.get("binary_sensor.ac_213c7f2_48")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "AC 213c7f2:48"
@ -456,7 +457,7 @@ async def test_options_add_remove_device(hass):
state = hass.states.get("binary_sensor.ac_213c7f2_48")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "AC 213c7f2:48"
device_registry = dr.async_get(hass)
@ -900,7 +901,7 @@ async def test_options_add_and_configure_device(hass):
state = hass.states.get("binary_sensor.pt2262_22670e")
assert state
assert state.state == "off"
assert state.state == STATE_UNKNOWN
assert state.attributes.get("friendly_name") == "PT2262 22670e"
device_registry = dr.async_get(hass)

View File

@ -17,6 +17,7 @@ from homeassistant.const import (
EVENT_STATE_CHANGED,
STATE_OFF,
STATE_ON,
STATE_UNKNOWN,
Platform,
)
import homeassistant.core as ha
@ -58,7 +59,7 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.tasmota_binary_sensor_1")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
assert not state.attributes.get(ATTR_ASSUMED_STATE)
# Test normal state update
@ -124,7 +125,7 @@ async def test_controlling_state_via_mqtt_switchname(hass, mqtt_mock, setup_tasm
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.custom_name")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
assert not state.attributes.get(ATTR_ASSUMED_STATE)
# Test normal state update
@ -183,7 +184,7 @@ async def test_pushon_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota)
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.tasmota_binary_sensor_1")
assert state.state == STATE_OFF
assert state.state == STATE_UNKNOWN
assert not state.attributes.get(ATTR_ASSUMED_STATE)
# Test normal state update
@ -260,14 +261,14 @@ async def test_off_delay(hass, mqtt_mock, setup_tasmota):
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
await hass.async_block_till_done()
assert events == ["off"]
assert events == ["unknown"]
async_fire_mqtt_message(
hass, "tasmota_49A3BC/stat/RESULT", '{"Switch1":{"Action":"ON"}}'
)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.tasmota_binary_sensor_1")
assert state.state == STATE_ON
assert events == ["off", "on"]
assert events == ["unknown", "on"]
async_fire_mqtt_message(
hass, "tasmota_49A3BC/stat/RESULT", '{"Switch1":{"Action":"ON"}}'
@ -275,13 +276,13 @@ async def test_off_delay(hass, mqtt_mock, setup_tasmota):
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.tasmota_binary_sensor_1")
assert state.state == STATE_ON
assert events == ["off", "on", "on"]
assert events == ["unknown", "on", "on"]
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1))
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.tasmota_binary_sensor_1")
assert state.state == STATE_OFF
assert events == ["off", "on", "on", "off"]
assert events == ["unknown", "on", "on", "off"]
async def test_availability_when_connection_lost(

View File

@ -13,6 +13,7 @@ from homeassistant.const import (
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import Context, CoreState
from homeassistant.helpers import entity_registry
@ -499,8 +500,10 @@ async def test_event(hass, start_ha):
)
async def test_template_delay_on_off(hass, start_ha):
"""Test binary sensor template delay on."""
assert hass.states.get("binary_sensor.test_on").state == OFF
assert hass.states.get("binary_sensor.test_off").state == OFF
# Ensure the initial state is not on
assert hass.states.get("binary_sensor.test_on").state != ON
assert hass.states.get("binary_sensor.test_off").state != ON
hass.states.async_set("input_number.delay", 5)
hass.states.async_set("sensor.test_state", ON)
await hass.async_block_till_done()
@ -722,10 +725,10 @@ async def test_no_update_template_match_all(hass, caplog):
hass.states.async_set("binary_sensor.test_sensor", "true")
assert len(hass.states.async_all()) == 5
assert hass.states.get("binary_sensor.all_state").state == OFF
assert hass.states.get("binary_sensor.all_icon").state == OFF
assert hass.states.get("binary_sensor.all_entity_picture").state == OFF
assert hass.states.get("binary_sensor.all_attribute").state == OFF
assert hass.states.get("binary_sensor.all_state").state == STATE_UNKNOWN
assert hass.states.get("binary_sensor.all_icon").state == STATE_UNKNOWN
assert hass.states.get("binary_sensor.all_entity_picture").state == STATE_UNKNOWN
assert hass.states.get("binary_sensor.all_attribute").state == STATE_UNKNOWN
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()

View File

@ -4,7 +4,7 @@ from unittest.mock import patch
from homeassistant import config as hass_config, setup
from homeassistant.components.trend import DOMAIN
from homeassistant.const import SERVICE_RELOAD
from homeassistant.const import SERVICE_RELOAD, STATE_UNKNOWN
import homeassistant.util.dt as dt_util
from tests.common import (
@ -307,7 +307,7 @@ class TestTrendBinarySensor:
self.hass.states.set("sensor.test_state", "Numeric")
self.hass.block_till_done()
state = self.hass.states.get("binary_sensor.test_trend_sensor")
assert state.state == "off"
assert state.state == STATE_UNKNOWN
def test_missing_attribute(self):
"""Test attribute down trend."""
@ -333,7 +333,7 @@ class TestTrendBinarySensor:
self.hass.states.set("sensor.test_state", "State", {"attr": "1"})
self.hass.block_till_done()
state = self.hass.states.get("binary_sensor.test_trend_sensor")
assert state.state == "off"
assert state.state == STATE_UNKNOWN
def test_invalid_name_does_not_create(self):
"""Test invalid name."""