Overhaul UniFi Protect NVR Disk sensors (#73197)

* Overhauls NVR Disk sensors

* Updates from latest version of pyunifiprotect
This commit is contained in:
Christopher Bailey 2022-06-08 20:13:56 -04:00 committed by GitHub
parent 5c49d0a761
commit 004ff8fb30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 187 additions and 22 deletions

View File

@ -6,6 +6,7 @@ from dataclasses import dataclass
import logging import logging
from pyunifiprotect.data import NVR, Camera, Event, Light, MountType, Sensor from pyunifiprotect.data import NVR, Camera, Event, Light, MountType, Sensor
from pyunifiprotect.data.nvr import UOSDisk
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
@ -13,7 +14,6 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_MODEL
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -131,7 +131,6 @@ DOORLOCK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
DISK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( DISK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription( ProtectBinaryEntityDescription(
key="disk_health", key="disk_health",
name="Disk {index} Health",
device_class=BinarySensorDeviceClass.PROBLEM, device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
@ -182,14 +181,18 @@ def _async_nvr_entities(
) -> list[ProtectDeviceEntity]: ) -> list[ProtectDeviceEntity]:
entities: list[ProtectDeviceEntity] = [] entities: list[ProtectDeviceEntity] = []
device = data.api.bootstrap.nvr device = data.api.bootstrap.nvr
for index, _ in enumerate(device.system_info.storage.devices): if device.system_info.ustorage is None:
return entities
for disk in device.system_info.ustorage.disks:
for description in DISK_SENSORS: for description in DISK_SENSORS:
entities.append( if not disk.has_disk:
ProtectDiskBinarySensor(data, device, description, index=index) continue
)
entities.append(ProtectDiskBinarySensor(data, device, description, disk))
_LOGGER.debug( _LOGGER.debug(
"Adding binary sensor entity %s", "Adding binary sensor entity %s",
(description.name or "{index}").format(index=index), f"{disk.type} {disk.slot}",
) )
return entities return entities
@ -216,6 +219,7 @@ class ProtectDeviceBinarySensor(ProtectDeviceEntity, BinarySensorEntity):
class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity): class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity):
"""A UniFi Protect NVR Disk Binary Sensor.""" """A UniFi Protect NVR Disk Binary Sensor."""
_disk: UOSDisk
entity_description: ProtectBinaryEntityDescription entity_description: ProtectBinaryEntityDescription
def __init__( def __init__(
@ -223,26 +227,35 @@ class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity):
data: ProtectData, data: ProtectData,
device: NVR, device: NVR,
description: ProtectBinaryEntityDescription, description: ProtectBinaryEntityDescription,
index: int, disk: UOSDisk,
) -> None: ) -> None:
"""Initialize the Binary Sensor.""" """Initialize the Binary Sensor."""
self._disk = disk
# backwards compat with old unique IDs
index = self._disk.slot - 1
description = copy(description) description = copy(description)
description.key = f"{description.key}_{index}" description.key = f"{description.key}_{index}"
description.name = (description.name or "{index}").format(index=index) description.name = f"{disk.type} {disk.slot}"
self._index = index
super().__init__(data, device, description) super().__init__(data, device, description)
@callback @callback
def _async_update_device_from_protect(self) -> None: def _async_update_device_from_protect(self) -> None:
super()._async_update_device_from_protect() super()._async_update_device_from_protect()
disks = self.device.system_info.storage.devices slot = self._disk.slot
disk_available = len(disks) > self._index self._attr_available = False
self._attr_available = self._attr_available and disk_available
if disk_available: if self.device.system_info.ustorage is None:
disk = disks[self._index] return
self._attr_is_on = not disk.healthy
self._attr_extra_state_attributes = {ATTR_MODEL: disk.model} for disk in self.device.system_info.ustorage.disks:
if disk.slot == slot:
self._disk = disk
self._attr_available = True
break
self._attr_is_on = not self._disk.is_healthy
class ProtectEventBinarySensor(EventThumbnailMixin, ProtectDeviceBinarySensor): class ProtectEventBinarySensor(EventThumbnailMixin, ProtectDeviceBinarySensor):

View File

@ -117,6 +117,158 @@
} }
] ]
}, },
"ustorage": {
"disks": [
{
"slot": 1,
"type": "HDD",
"model": "ST16000VE000-2L2103",
"serial": "ABCD1234",
"firmware": "EV02",
"rpm": 7200,
"ata": "ACS-4",
"sata": "SATA 3.3",
"action": "expanding",
"healthy": "good",
"state": "expanding",
"reason": null,
"temperature": 52,
"poweronhrs": 4242,
"life_span": null,
"bad_sector": 0,
"threshold": 10,
"progress": 21.390607518939174,
"estimate": 234395.73300748435
},
{
"slot": 2,
"type": "HDD",
"model": "ST16000VE000-2L2103",
"serial": "ABCD1234",
"firmware": "EV02",
"rpm": 7200,
"ata": "ACS-4",
"sata": "SATA 3.3",
"action": "expanding",
"healthy": "good",
"state": "expanding",
"reason": null,
"temperature": 52,
"poweronhrs": 4242,
"life_span": null,
"bad_sector": 0,
"threshold": 10,
"progress": 21.390607518939174,
"estimate": 234395.73300748435
},
{
"slot": 3,
"type": "HDD",
"model": "ST16000VE000-2L2103",
"serial": "ABCD1234",
"firmware": "EV02",
"rpm": 7200,
"ata": "ACS-4",
"sata": "SATA 3.3",
"action": "expanding",
"healthy": "good",
"state": "expanding",
"reason": null,
"temperature": 51,
"poweronhrs": 4242,
"life_span": null,
"bad_sector": 0,
"threshold": 10,
"progress": 21.390607518939174,
"estimate": 234395.73300748435
},
{
"slot": 4,
"type": "HDD",
"model": "ST16000VE000-2L2103",
"serial": "ABCD1234",
"firmware": "EV02",
"rpm": 7200,
"ata": "ACS-4",
"sata": "SATA 3.3",
"action": "expanding",
"healthy": "good",
"state": "expanding",
"reason": null,
"temperature": 50,
"poweronhrs": 2443,
"life_span": null,
"bad_sector": 0,
"threshold": 10,
"progress": 21.390607518939174,
"estimate": 234395.73300748435
},
{
"slot": 5,
"type": "HDD",
"model": "ST16000VE000-2L2103",
"serial": "ABCD1234",
"firmware": "EV02",
"rpm": 7200,
"ata": "ACS-4",
"sata": "SATA 3.3",
"action": "expanding",
"healthy": "good",
"state": "expanding",
"reason": null,
"temperature": 50,
"poweronhrs": 783,
"life_span": null,
"bad_sector": 0,
"threshold": 10,
"progress": 21.390607518939174,
"estimate": 234395.73300748435
},
{
"slot": 6,
"state": "nodisk"
},
{
"slot": 7,
"type": "HDD",
"model": "ST16000VE002-3BR101",
"serial": "ABCD1234",
"firmware": "EV01",
"rpm": 7200,
"ata": "ACS-4",
"sata": "SATA 3.3",
"action": "expanding",
"healthy": "good",
"state": "expanding",
"reason": null,
"temperature": 45,
"poweronhrs": 18,
"life_span": null,
"bad_sector": 0,
"threshold": 10,
"progress": 21.390607518939174,
"estimate": 234395.73300748435
}
],
"space": [
{
"device": "md3",
"total_bytes": 63713403555840,
"used_bytes": 57006577086464,
"action": "expanding",
"progress": 21.390607518939174,
"estimate": 234395.73300748435
},
{
"device": "md0",
"total_bytes": 0,
"used_bytes": 0,
"action": "syncing",
"progress": 0,
"estimate": null
}
]
},
"tmpfs": { "tmpfs": {
"available": 934204, "available": 934204,
"total": 1048576, "total": 1048576,

View File

@ -71,7 +71,7 @@ async def camera_fixture(
await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert_entity_counts(hass, Platform.BINARY_SENSOR, 3, 3) assert_entity_counts(hass, Platform.BINARY_SENSOR, 9, 9)
yield camera_obj yield camera_obj
@ -103,7 +103,7 @@ async def light_fixture(
await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert_entity_counts(hass, Platform.BINARY_SENSOR, 2, 2) assert_entity_counts(hass, Platform.BINARY_SENSOR, 8, 8)
yield light_obj yield light_obj
@ -138,7 +138,7 @@ async def camera_none_fixture(
await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert_entity_counts(hass, Platform.BINARY_SENSOR, 2, 2) assert_entity_counts(hass, Platform.BINARY_SENSOR, 8, 8)
yield camera_obj yield camera_obj
@ -179,7 +179,7 @@ async def sensor_fixture(
await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert_entity_counts(hass, Platform.BINARY_SENSOR, 4, 4) assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10)
yield sensor_obj yield sensor_obj
@ -215,7 +215,7 @@ async def sensor_none_fixture(
await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert_entity_counts(hass, Platform.BINARY_SENSOR, 4, 4) assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10)
yield sensor_obj yield sensor_obj