Skip to content

Commit

Permalink
Add updates sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
dougiteixeira committed Nov 12, 2023
1 parent 8e6240f commit 38aab67
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 5 deletions.
26 changes: 26 additions & 0 deletions custom_components/proxmoxve/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ class ProxmoxBinarySensorEntityDescription(
),
)

PROXMOX_BINARYSENSOR_UPDATES: Final[tuple[ProxmoxBinarySensorEntityDescription, ...]] = (
ProxmoxBinarySensorEntityDescription(
key=ProxmoxKeyAPIParse.UPDATE_AVAIL,
name="Updates packages",
device_class=BinarySensorDeviceClass.UPDATE,
on_value=True,
translation_key="update_avail",
),
)

PROXMOX_BINARYSENSOR_VM: Final[tuple[ProxmoxBinarySensorEntityDescription, ...]] = (
ProxmoxBinarySensorEntityDescription(
key=ProxmoxKeyAPIParse.STATUS,
Expand Down Expand Up @@ -91,6 +101,22 @@ async def async_setup_entry(
resource_id=node,
)
)
coordinator_updates = coordinators[f"{ProxmoxType.Update}_{node}"]
for description in PROXMOX_BINARYSENSOR_UPDATES:
sensors.append(
create_binary_sensor(
coordinator=coordinator_updates,
config_entry=config_entry,
info_device=device_info(
hass=hass,
config_entry=config_entry,
api_category=ProxmoxType.Update,
node=node,
),
description=description,
resource_id=node,
)
)

for vm_id in config_entry.data[CONF_QEMU]:
coordinator = coordinators[vm_id]
Expand Down
117 changes: 116 additions & 1 deletion custom_components/proxmoxve/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import CONF_NODE, DOMAIN, LOGGER, UPDATE_INTERVAL, ProxmoxType
from .models import ProxmoxLXCData, ProxmoxNodeData, ProxmoxStorageData, ProxmoxVMData
from .models import ProxmoxLXCData, ProxmoxNodeData, ProxmoxStorageData, ProxmoxUpdateData, ProxmoxVMData


class ProxmoxCoordinator(
Expand Down Expand Up @@ -441,6 +441,8 @@ def poll_api() -> dict[str, Any] | None:

#if api_status is None or "content" not in api_status:
# raise UpdateFailed(f"Storage {self.resource_id} unable to be found")
if api_status is None or "content" not in api_status:
raise UpdateFailed(f"Storage {self.resource_id} unable to be found")

update_device_via(self, ProxmoxType.Storage)
return ProxmoxStorageData(
Expand All @@ -452,6 +454,113 @@ def poll_api() -> dict[str, Any] | None:
content=api_status["content"]
)

class ProxmoxUpdateCoordinator(ProxmoxCoordinator):
"""Proxmox VE Update data update coordinator."""

def __init__(
self,
hass: HomeAssistant,
proxmox: ProxmoxAPI,
api_category: str,
node_name: int,
) -> None:
"""Initialize the Proxmox LXC coordinator."""

super().__init__(
hass,
LOGGER,
name=f"proxmox_coordinator_{api_category}_{node_name}",
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)

self.hass = hass
self.config_entry: ConfigEntry = self.config_entry
self.proxmox = proxmox
self.node_name = node_name
self.resource_id = f"{api_category}_{node_name}"

async def _async_update_data(self) -> ProxmoxLXCData:
"""Update data for Proxmox LXC."""

def poll_api() -> dict[str, Any] | None:
"""Return data from the Proxmox LXC API."""
try:
api_status = None

if self.node_name is not None:
api_status = (
self.proxmox.nodes(self.node_name)
.apt
.update.get()
)

if self.node_name is None:
raise UpdateFailed(f"{self.resource_id} node not found")

except (
AuthenticationError,
SSLError,
ConnectTimeout,
) as error:
raise UpdateFailed(error) from error
except ResourceException as error:
if error.status_code == 403:
async_create_issue(
self.hass,
DOMAIN,
f"{self.config_entry.entry_id}_{self.resource_id}_forbiden",
is_fixable=False,
severity=IssueSeverity.ERROR,
translation_key="resource_exception_forbiden",
translation_placeholders={
"resource": f"Update {self.node_name}",
"user": self.config_entry.data[CONF_USERNAME],
},
)
raise UpdateFailed(
"User not allowed to access the resource, check user permissions as per the documentation."
) from error

async_delete_issue(
self.hass,
DOMAIN,
f"{self.config_entry.entry_id}_{self.resource_id}_forbiden",
)

LOGGER.debug("API Response - Update: %s", api_status)
return api_status

api_status = await self.hass.async_add_executor_job(poll_api)

if api_status is None:
return ProxmoxUpdateData(
type=ProxmoxType.Update,
node=self.node_name,
total=0,
updates_list=[],
update=False,
)

list = []
total = 0
for update in api_status:
list.append(f"{update['Title']} - {update['Version']}")
total += 1

list.sort()

update_avail=False
if total >0:
update_avail=True

return ProxmoxUpdateData(
type=ProxmoxType.Update,
node=self.node_name,
total=total,
updates_list=list,
update=update_avail,
)


def update_device_via(
self,
Expand Down Expand Up @@ -518,6 +627,12 @@ async def verify_permissions_error(
except ResourceException as error:
if error.status_code == 403:
permissions = True
if resource_type == ProxmoxType.Update:
try:
self.proxmox.nodes(resource_node).apt.update.get()
except ResourceException as error:
if error.status_code == 403:
permissions = True

if permissions:
async_create_issue(
Expand Down
13 changes: 12 additions & 1 deletion custom_components/proxmoxve/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,15 @@ class ProxmoxStorageData:
content: str
disk_free: float
disk_used: float
disk_total: float
disk_total: float


@dataclasses.dataclass
class ProxmoxUpdateData:
"""Data parsed from the Proxmox API for Updates."""

type: str
node: str
updates_list: list
total: float
update: bool
47 changes: 44 additions & 3 deletions custom_components/proxmoxve/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections.abc import Callable
from dataclasses import dataclass
from datetime import timedelta
from typing import Any, Final
from typing import Any, Final, Mapping

from homeassistant.components.sensor import (
SensorDeviceClass,
Expand Down Expand Up @@ -43,6 +43,7 @@ class ProxmoxSensorEntityDescription(ProxmoxEntityDescription, SensorEntityDescr
conversion_fn: Callable | None = None # conversion factor to be applied to units
value_fn: Callable[[Any], bool | str] | None = None
api_category: ProxmoxType | None = None # Set when the sensor applies to only QEMU or LXC, if None applies to both.
extra_attrs: list[str] | None = None


PROXMOX_SENSOR_DISK: Final[tuple[ProxmoxSensorEntityDescription, ...]] = (
Expand Down Expand Up @@ -282,7 +283,17 @@ class ProxmoxSensorEntityDescription(ProxmoxEntityDescription, SensorEntityDescr
translation_key="cpu_used",
),
)

PROXMOX_SENSOR_UPDATE: Final[tuple[ProxmoxSensorEntityDescription, ...]] = (
ProxmoxSensorEntityDescription(
key=ProxmoxKeyAPIParse.UPDATE_TOTAL,
name="Total updates",
icon="mdi:update",
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
translation_key="updates_total",
extra_attrs=[ProxmoxKeyAPIParse.UPDATE_LIST],
),
)
PROXMOX_SENSOR_NODES: Final[tuple[ProxmoxSensorEntityDescription, ...]] = (
*PROXMOX_SENSOR_CPU,
*PROXMOX_SENSOR_DISK,
Expand Down Expand Up @@ -358,6 +369,22 @@ async def async_setup_entry(
config_entry=config_entry,
)
)
coordinator_updates = coordinators[f"{ProxmoxType.Update}_{node}"]
for description in PROXMOX_SENSOR_UPDATE:
sensors.append(
create_sensor(
coordinator=coordinator_updates,
info_device=device_info(
hass=hass,
config_entry=config_entry,
api_category=ProxmoxType.Update,
node=node,
),
description=description,
resource_id=node,
config_entry=config_entry,
)
)

for vm_id in config_entry.data[CONF_QEMU]:
coordinator = coordinators[vm_id]
Expand Down Expand Up @@ -471,7 +498,7 @@ def native_value(self) -> StateType:
if not getattr(data, self.entity_description.key, False):
if value := self.entity_description.value_fn:
native_value = value(data)
elif self.entity_description.key is ProxmoxKeyAPIParse.CPU:
elif self.entity_description.key in (ProxmoxKeyAPIParse.CPU, ProxmoxKeyAPIParse.UPDATE_TOTAL):
return 0
else:
return None
Expand All @@ -488,3 +515,17 @@ def available(self) -> bool:
"""Return sensor availability."""

return super().available and self.coordinator.data is not None

@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return the extra attributes of the sensor."""
if self.entity_description.extra_attrs is None:
return None

if (data := self.coordinator.data) is None:
return None

return {
attr: getattr(data, attr, False)
for attr in self.entity_description.extra_attrs
}
11 changes: 11 additions & 0 deletions custom_components/proxmoxve/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@
},
"health": {
"name": "Health"
},
"update_avail": {
"name": "Updates packages"
}
},
"button": {
Expand Down Expand Up @@ -231,6 +234,14 @@
"swap_used_perc": {
"name": "Swap used percentage"
},
"updates_total": {
"name": "Total updates",
"state_attributes": {
"updates_list": {
"name": "Updates list"
}
}
},
"uptime": {
"name": "Uptime"
}
Expand Down
11 changes: 11 additions & 0 deletions custom_components/proxmoxve/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@
},
"health": {
"name": "Health"
},
"update_avail": {
"name": "Updates packages"
}
},
"button": {
Expand Down Expand Up @@ -231,6 +234,14 @@
"swap_used_perc": {
"name": "Swap used percentage"
},
"updates_total": {
"name": "Total updates",
"state_attributes": {
"updates_list": {
"name": "Updates list"
}
}
},
"uptime": {
"name": "Uptime"
}
Expand Down
16 changes: 16 additions & 0 deletions custom_components/proxmoxve/translations/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
},
"health": {
"name": "Integridade"
},
"update_avail": {
"name": "Pacotes de atualizações",
"state_attributes": {
"updates": {
"name": "Updates list"
}
}
}
},
"button": {
Expand Down Expand Up @@ -142,6 +150,14 @@
"swap_used_perc": {
"name": "Swap percentual em uso"
},
"updates_total": {
"name": "Total de atualizações",
"state_attributes": {
"updates_list": {
"name": "Lista de atualizações"
}
}
},
"uptime": {
"name": "Tempo de atividade"
}
Expand Down

0 comments on commit 38aab67

Please sign in to comment.