Skip to content

Commit

Permalink
Merge pull request #650 from blakeblackshear/dev
Browse files Browse the repository at this point in the history
Merge dev into master for 5.1 release
  • Loading branch information
dermotduffy committed Mar 20, 2024
2 parents d8fc7bd + 2d86556 commit 94c5c5f
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 7 deletions.
1 change: 1 addition & 0 deletions custom_components/frigate/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
ICON_PERSON = "mdi:human"
ICON_SERVER = "mdi:server"
ICON_SPEEDOMETER = "mdi:speedometer"
ICON_WAVEFORM = "mdi:waveform"

ICON_DEFAULT_ON = "mdi:home"

Expand Down
4 changes: 2 additions & 2 deletions custom_components/frigate/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"documentation": "https://github.com/blakeblackshear/frigate",
"iot_class": "local_push",
"issue_tracker": "https://github.com/blakeblackshear/frigate-hass-integration/issues",
"requirements": ["pytz==2022.7"],
"version": "5.0.1"
"requirements": ["pytz"],
"version": "5.1.0"
}
90 changes: 88 additions & 2 deletions custom_components/frigate/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from typing import Any

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_URL, PERCENTAGE, UnitOfTemperature
from homeassistant.const import (
CONF_URL,
PERCENTAGE,
UnitOfSoundPressure,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
Expand All @@ -24,7 +29,13 @@
get_zones,
)
from .const import ATTR_CONFIG, ATTR_COORDINATOR, DOMAIN, FPS, MS, NAME
from .icons import ICON_CORAL, ICON_SERVER, ICON_SPEEDOMETER, get_icon_from_type
from .icons import (
ICON_CORAL,
ICON_SERVER,
ICON_SPEEDOMETER,
ICON_WAVEFORM,
get_icon_from_type,
)

_LOGGER: logging.Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -75,6 +86,9 @@ async def async_setup_entry(
]
)

if frigate_config["cameras"][name]["audio"]["enabled_in_config"]:
entities.append(CameraSoundSensor(coordinator, entry, name))

frigate_config = hass.data[DOMAIN][entry.entry_id][ATTR_CONFIG]
entities.extend(
[
Expand Down Expand Up @@ -394,6 +408,78 @@ def icon(self) -> str:
return ICON_SPEEDOMETER


class CameraSoundSensor(FrigateEntity, CoordinatorEntity): # type: ignore[misc]
"""Frigate Camera Sound Level class."""

def __init__(
self,
coordinator: FrigateDataUpdateCoordinator,
config_entry: ConfigEntry,
cam_name: str,
) -> None:
"""Construct a CameraSoundSensor."""
FrigateEntity.__init__(self, config_entry)
CoordinatorEntity.__init__(self, coordinator)
self._cam_name = cam_name
self._attr_entity_registry_enabled_default = True

@property
def unique_id(self) -> str:
"""Return a unique ID to use for this entity."""
return get_frigate_entity_unique_id(
self._config_entry.entry_id,
"sensor_sound_level",
f"{self._cam_name}_dB",
)

@property
def device_info(self) -> DeviceInfo:
"""Get device information."""
return {
"identifiers": {
get_frigate_device_identifier(self._config_entry, self._cam_name)
},
"via_device": get_frigate_device_identifier(self._config_entry),
"name": get_friendly_name(self._cam_name),
"model": self._get_model(),
"configuration_url": f"{self._config_entry.data.get(CONF_URL)}/cameras/{self._cam_name}",
"manufacturer": NAME,
}

@property
def name(self) -> str:
"""Return the name of the sensor."""
return "sound level"

@property
def unit_of_measurement(self) -> Any:
"""Return the unit of measurement of the sensor."""
return UnitOfSoundPressure.DECIBEL

@property
def state(self) -> int | None:
"""Return the state of the sensor."""

if self.coordinator.data:
data = (
self.coordinator.data.get("cameras", {})
.get(self._cam_name, {})
.get("audio_dBFS")
)

if data is not None:
try:
return round(float(data))
except ValueError:
pass
return None

@property
def icon(self) -> str:
"""Return the icon of the sensor."""
return ICON_WAVEFORM


class FrigateObjectCountSensor(FrigateMQTTEntity):
"""Frigate Motion Sensor class."""

Expand Down
4 changes: 2 additions & 2 deletions custom_components/frigate/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"config": {
"step": {
"user": {
"description": "URL, die Sie für den Zugriff auf Frigate verwenden (z. B. \"http://fregate:5000/\")\n\nWenn Sie HassOS mit dem Addon verwenden, sollte die URL „http://ccab4aaf-fregate:5000/“ lauten\n\nHome Assistant benötigt für alle Funktionen Zugriff auf Port 5000 (api) und 1935 (rtmp).\n\nDie Integration richtet Sensoren, Kameras und Medienbrowser-Funktionen ein.\n\nSensoren:\n- Statistiken zur Überwachung der Frigate-Leistung\n- Objektzählungen für alle Zonen und Kameras\n\nKameras:\n- Kameras für Bild des zuletzt erkannten Objekts für jede Kamera\n- Kameraeinheiten mit Stream-Unterstützung (erfordert RTMP)\n\nMedienbrowser:\n- Umfangreiche Benutzeroberfläche mit Vorschaubildern zum Durchsuchen von Event-Clips\n- Umfangreiche Benutzeroberfläche zum Durchsuchen von 24/7-Aufzeichnungen nach Monat, Tag, Kamera und Uhrzeit\n\nAPI:\n- Benachrichtigungs-API mit öffentlich zugänglichen Endpunkten für Bilder in Benachrichtigungen",
"description": "URL, die Sie für den Zugriff auf Frigate verwenden (z. B. \"http://frigate:5000/\")\n\nWenn Sie HassOS mit dem Addon verwenden, sollte die URL „http://ccab4aaf-frigate:5000/“ lauten\n\nHome Assistant benötigt für alle Funktionen Zugriff auf Port 5000 (api) und 1935 (rtmp).\n\nDie Integration richtet Sensoren, Kameras und Medienbrowser-Funktionen ein.\n\nSensoren:\n- Statistiken zur Überwachung der Frigate-Leistung\n- Objektzählungen für alle Zonen und Kameras\n\nKameras:\n- Kameras für Bild des zuletzt erkannten Objekts für jede Kamera\n- Kameraeinheiten mit Stream-Unterstützung (erfordert RTMP)\n\nMedienbrowser:\n- Umfangreiche Benutzeroberfläche mit Vorschaubildern zum Durchsuchen von Event-Clips\n- Umfangreiche Benutzeroberfläche zum Durchsuchen von 24/7-Aufzeichnungen nach Monat, Tag, Kamera und Uhrzeit\n\nAPI:\n- Benachrichtigungs-API mit öffentlich zugänglichen Endpunkten für Bilder in Benachrichtigungen",
"data": {
"url": "URL"
}
Expand Down Expand Up @@ -32,4 +32,4 @@
"only_advanced_options": "Der erweiterte Modus ist deaktiviert und es stehen nur erweiterte Optionen zur Verfügung"
}
}
}
}
36 changes: 36 additions & 0 deletions custom_components/frigate/translations/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"config": {
"step": {
"user": {
"description": "URL que vous utilisez pour accéder à Frigate (par exemple, `http://frigate:5000/`)\n\nSi vous utilisez HassOS avec l'addon, l'URL devrait être `http://ccab4aaf-frigate:5000/`\n\nHome Assistant a besoin d'accès au port 5000 (api) et 1935 (rtmp) pour toutes les fonctionnalités.\n\nL'intégration configurera des capteurs, des caméras et la fonctionnalité de navigateur multimédia.\n\nCapteurs :\n- Statistiques pour surveiller la performance de Frigate\n- Comptes d'objets pour toutes les zones et caméras\n\nCaméras :\n- Caméras pour l'image du dernier objet détecté pour chaque caméra\n- Entités de caméra avec support de flux (nécessite RTMP)\n\nNavigateur multimédia :\n- Interface riche avec miniatures pour parcourir les clips d'événements\n- Interface riche pour parcourir les enregistrements 24/7 par mois, jour, caméra, heure\n\nAPI :\n- API de notification avec des points de terminaison publics pour les images dans les notifications",
"data": {
"url": "URL"
}
}
},
"error": {
"cannot_connect": "Échec de la connexion",
"invalid_url": "URL invalide"
},
"abort": {
"already_configured": "L'appareil est déjà configuré"
}
},
"options": {
"step": {
"init": {
"data": {
"enable_webrtc": "Activer WebRTC pour les flux de caméra",
"rtmp_url_template": "Modèle d'URL RTMP (voir la documentation)",
"rtsp_url_template": "Modèle d'URL RTSP (voir la documentation)",
"media_browser_enable": "Activer le navigateur multimédia",
"notification_proxy_enable": "Activer le proxy d'événement de notification non authentifié",
"notification_proxy_expire_after_seconds": "Interdire l'accès à la notification non authentifiée après secondes (0=jamais)"
}
}
},
"abort": {
"only_advanced_options": "Le mode avancé est désactivé et il n'y a que des options avancées"
}
}
}
2 changes: 2 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
TEST_SENSOR_FRONT_DOOR_FFMPEG_CPU_USAGE = "sensor.front_door_ffmpeg_cpu_usage"
TEST_SENSOR_FRONT_DOOR_PROCESS_FPS_ENTITY_ID = "sensor.front_door_process_fps"
TEST_SENSOR_FRONT_DOOR_SKIPPED_FPS_ENTITY_ID = "sensor.front_door_skipped_fps"
TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID = "sensor.front_door_sound_level"
TEST_SENSOR_FRIGATE_STATUS_ENTITY_ID = "sensor.frigate_status"
TEST_UPDATE_FRIGATE_CONTAINER_ENTITY_ID = "update.frigate_server"

Expand Down Expand Up @@ -181,6 +182,7 @@
"ffmpeg_pid": 54,
"process_fps": 4.0,
"skipped_fps": 0.0,
"audio_dBFS": -12,
},
},
"detection_fps": 13.7,
Expand Down
45 changes: 44 additions & 1 deletion tests/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
ICON_PERSON,
ICON_SERVER,
ICON_SPEEDOMETER,
ICON_WAVEFORM,
)
from homeassistant.const import PERCENTAGE, UnitOfTemperature
from homeassistant.const import PERCENTAGE, UnitOfSoundPressure, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.util.dt as dt_util
Expand All @@ -51,6 +52,7 @@
TEST_SENSOR_FRONT_DOOR_PERSON_ENTITY_ID,
TEST_SENSOR_FRONT_DOOR_PROCESS_FPS_ENTITY_ID,
TEST_SENSOR_FRONT_DOOR_SKIPPED_FPS_ENTITY_ID,
TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID,
TEST_SENSOR_GPU_LOAD_ENTITY_ID,
TEST_SENSOR_STEPS_ALL_ENTITY_ID,
TEST_SENSOR_STEPS_PERSON_ENTITY_ID,
Expand Down Expand Up @@ -414,6 +416,47 @@ async def test_camera_fps_sensor(hass: HomeAssistant) -> None:
assert entity_state.state == "unknown"


async def test_camera_audio_sensor(hass: HomeAssistant) -> None:
"""Test CameraAudioLevel state."""

client = create_mock_frigate_client()
await setup_mock_frigate_config_entry(hass, client=client)
await enable_and_load_entity(hass, client, TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID)

entity_state = hass.states.get(TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID)
assert entity_state
assert entity_state.state == "-12"
assert entity_state.attributes["icon"] == ICON_WAVEFORM
assert entity_state.attributes["unit_of_measurement"] == UnitOfSoundPressure.DECIBEL

stats: dict[str, Any] = copy.deepcopy(TEST_STATS)
client.async_get_stats = AsyncMock(return_value=stats)

stats["cameras"]["front_door"]["audio_dBFS"] = -3.9
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()

entity_state = hass.states.get(TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID)
assert entity_state
assert entity_state.state == "-4"

stats["cameras"]["front_door"]["audio_dBFS"] = None
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()

entity_state = hass.states.get(TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID)
assert entity_state
assert entity_state.state == "unknown"

stats["cameras"]["front_door"]["audio_dBFS"] = "NOT_A_NUMBER"
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done()

entity_state = hass.states.get(TEST_SENSOR_FRONT_DOOR_SOUND_LEVEL_ID)
assert entity_state
assert entity_state.state == "unknown"


async def test_camera_cpu_usage_sensor(hass: HomeAssistant) -> None:
"""Test CameraProcessCpuSensor state."""

Expand Down

0 comments on commit 94c5c5f

Please sign in to comment.