Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an improve_contrast switch #230

Merged
merged 12 commits into from
Apr 21, 2022
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Provides the following:
- Sensor entities (Camera FPS, Detection FPS, Process FPS, Skipped FPS, Objects detected)
- Binary Sensor entities (Object motion)
- Camera entities (Live view, Object detected snapshot)
- Switch entities (Clips, Detection, Snapshots)
- Switch entities (Clips, Detection, Snapshots, Improve Contrast)
- Support for multiple Frigate instances.

## Installation
Expand Down
1 change: 1 addition & 0 deletions custom_components/frigate/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ICON_OTHER = "mdi:shield-alert"
ICON_PERSON = "mdi:shield-account"
ICON_SPEEDOMETER = "mdi:speedometer"
ICON_CONTRAST = "mdi:contrast-circle"

# Platforms
BINARY_SENSOR = "binary_sensor"
Expand Down
16 changes: 12 additions & 4 deletions custom_components/frigate/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .const import (
ATTR_CONFIG,
DOMAIN,
ICON_CONTRAST,
ICON_FILM_MULTIPLE,
ICON_IMAGE_MULTIPLE,
ICON_MOTION_SENSOR,
Expand All @@ -41,9 +42,10 @@ async def async_setup_entry(
for camera in frigate_config["cameras"].keys():
entities.extend(
[
FrigateSwitch(entry, frigate_config, camera, "detect"),
FrigateSwitch(entry, frigate_config, camera, "recordings"),
FrigateSwitch(entry, frigate_config, camera, "snapshots"),
FrigateSwitch(entry, frigate_config, camera, "detect", True),
FrigateSwitch(entry, frigate_config, camera, "recordings", True),
FrigateSwitch(entry, frigate_config, camera, "snapshots", True),
FrigateSwitch(entry, frigate_config, camera, "improve_contrast", False),
]
)
async_add_entities(entities)
Expand All @@ -53,13 +55,15 @@ class FrigateSwitch(FrigateMQTTEntity, SwitchEntity): # type: ignore[misc]
"""Frigate Switch class."""

_attr_entity_category = EntityCategory.CONFIG


def __init__(
self,
config_entry: ConfigEntry,
frigate_config: dict[str, Any],
cam_name: str,
switch_name: str,
default_enabled: bool,
) -> None:
"""Construct a FrigateSwitch."""

Expand All @@ -71,10 +75,14 @@ def __init__(
f"/{self._cam_name}/{self._switch_name}/set"
)

self._attr_entity_registry_enabled_default = default_enabled

if self._switch_name == "snapshots":
self._icon = ICON_IMAGE_MULTIPLE
elif self._switch_name == "recordings":
self._icon = ICON_FILM_MULTIPLE
elif self._switch_name == "improve_contrast":
self._icon = ICON_CONTRAST
else:
self._icon = ICON_MOTION_SENSOR

Expand Down Expand Up @@ -121,7 +129,7 @@ def device_info(self) -> DeviceInfo:
@property
def name(self) -> str:
"""Return the name of the sensor."""
return f"{get_friendly_name(self._cam_name)} {self._switch_name}".title()
return f"{get_friendly_name(self._cam_name)} {get_friendly_name(self._switch_name)}".title()

@property
def is_on(self) -> bool:
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
TEST_SWITCH_FRONT_DOOR_DETECT_ENTITY_ID = "switch.front_door_detect"
TEST_SWITCH_FRONT_DOOR_SNAPSHOTS_ENTITY_ID = "switch.front_door_snapshots"
TEST_SWITCH_FRONT_DOOR_RECORDINGS_ENTITY_ID = "switch.front_door_recordings"
TEST_SWITCH_FRONT_DOOR_IMPROVE_CONTRAST_ENTITY_ID = "switch.front_door_improve_contrast"
TEST_SENSOR_STEPS_PERSON_ENTITY_ID = "sensor.steps_person"
TEST_SENSOR_STEPS_ALL_ENTITY_ID = "sensor.steps_all"
TEST_SENSOR_FRONT_DOOR_PERSON_ENTITY_ID = "sensor.front_door_person"
Expand Down
38 changes: 34 additions & 4 deletions tests/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,42 @@
TEST_CONFIG_ENTRY_ID,
TEST_SERVER_VERSION,
TEST_SWITCH_FRONT_DOOR_DETECT_ENTITY_ID,
TEST_SWITCH_FRONT_DOOR_IMPROVE_CONTRAST_ENTITY_ID,
TEST_SWITCH_FRONT_DOOR_RECORDINGS_ENTITY_ID,
TEST_SWITCH_FRONT_DOOR_SNAPSHOTS_ENTITY_ID,
setup_mock_frigate_config_entry,
)

_LOGGER = logging.getLogger(__name__)

SWITCH_ENTITY_IDS = [
ENABLED_SWITCH_ENTITY_IDS = [
TEST_SWITCH_FRONT_DOOR_DETECT_ENTITY_ID,
TEST_SWITCH_FRONT_DOOR_RECORDINGS_ENTITY_ID,
TEST_SWITCH_FRONT_DOOR_SNAPSHOTS_ENTITY_ID,
]

DISABLED_SWITCH_ENTITY_IDS = [
TEST_SWITCH_FRONT_DOOR_IMPROVE_CONTRAST_ENTITY_ID,
]


async def test_switch_state(hass: HomeAssistant) -> None:
"""Verify a successful binary sensor setup."""
await setup_mock_frigate_config_entry(hass)

for entity_id in SWITCH_ENTITY_IDS:
for entity_id in ENABLED_SWITCH_ENTITY_IDS:
entity_state = hass.states.get(entity_id)
assert entity_state
assert entity_state.state == "unavailable"

for entity_id in DISABLED_SWITCH_ENTITY_IDS:
entity_state = hass.states.get(entity_id)
assert not entity_state

async_fire_mqtt_message(hass, "frigate/available", "online")
await hass.async_block_till_done()

for entity_id in SWITCH_ENTITY_IDS:
for entity_id in ENABLED_SWITCH_ENTITY_IDS:
entity_state = hass.states.get(entity_id)
assert entity_state
assert entity_state.state == "off"
Expand Down Expand Up @@ -124,7 +133,7 @@ async def test_switch_device_info(hass: HomeAssistant) -> None:
entry.entity_id
for entry in er.async_entries_for_device(entity_registry, device.id)
]
for entity_id in SWITCH_ENTITY_IDS:
for entity_id in ENABLED_SWITCH_ENTITY_IDS:
assert entity_id in entities_from_device


Expand All @@ -136,6 +145,7 @@ async def test_switch_icon(hass: HomeAssistant) -> None:
TEST_SWITCH_FRONT_DOOR_DETECT_ENTITY_ID: "hass:motion-sensor",
TEST_SWITCH_FRONT_DOOR_RECORDINGS_ENTITY_ID: "mdi:filmstrip-box-multiple",
TEST_SWITCH_FRONT_DOOR_SNAPSHOTS_ENTITY_ID: "mdi:image-multiple",
TEST_SWITCH_FRONT_DOOR_IMPROVE_CONTRAST_ENTITY_ID: "mdi:contrast-circle",
}

for entity_id, icon in expected_results.items():
Expand All @@ -154,3 +164,23 @@ async def test_switch_unique_id(hass: HomeAssistant) -> None:
assert (
registry_entry.unique_id == f"{TEST_CONFIG_ENTRY_ID}:switch:front_door_detect"
)


async def test_switch_improve_contrast_can_be_enabled(hass: HomeAssistant) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better would be to use @pytest.mark.parametrize here so that this test gets run for all members of DISABLED_SWITCH_ENTITY_IDS (so that when we add more switches there this test will "just work").

Take a look at the sensor test for inspiration: https://github.com/blakeblackshear/frigate-hass-integration/blob/master/tests/test_sensor.py#L92

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea. So, something like this? Not sure if I've got the syntax right.

@pytest.mark.parametrize("disabled_switch_name", DISABLED_SWITCH_ENTITY_IDS)
async def test_disabled_switch_can_be_enabled(
    disabled_switch_name: Any, hass: HomeAssistant
) -> None:
    """Verify disabled switches can be enabled."""
    await setup_mock_frigate_config_entry(hass)
    entity_registry = er.async_get(hass)

    # Test original entity is disabled as expected
    entry = entity_registry.async_get(disabled_switch_name)
    assert entry
    assert entry.disabled
    assert entry.disabled_by == er.DISABLED_INTEGRATION
    entity_state = hass.states.get(disabled_switch_name)
    assert not entity_state

    # Update and test that entity is now enabled
    updated_entry = entity_registry.async_update_entity(
        disabled_switch_name, disabled_by=None
    )
    assert not updated_entry.disabled

Really appreciate your patience as I learn this!

Copy link
Collaborator

@dermotduffy dermotduffy Mar 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, exactly.

Minor:

  • I'd call disabled_switch_name as disabled_entity_id instead (slightly clearer)
  • I think you should be able to type annotate it with str instead of Any

"""Verify `improve_contrast` switch can be enabled."""
await setup_mock_frigate_config_entry(hass)
entity_registry = er.async_get(hass)

# Test original entity is disabled as expected
entry = entity_registry.async_get(TEST_SWITCH_FRONT_DOOR_IMPROVE_CONTRAST_ENTITY_ID)
assert entry
assert entry.disabled
assert entry.disabled_by == er.DISABLED_INTEGRATION
entity_state = hass.states.get(TEST_SWITCH_FRONT_DOOR_IMPROVE_CONTRAST_ENTITY_ID)
assert not entity_state

# Update and test that entity is now enabled
updated_entry = entity_registry.async_update_entity(
TEST_SWITCH_FRONT_DOOR_IMPROVE_CONTRAST_ENTITY_ID, disabled_by=None
)
assert not updated_entry.disabled