Skip to content

Commit

Permalink
Added Extra Sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
MrD3y5eL committed Oct 15, 2024
1 parent 0cf5f5b commit ef77a03
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 37 deletions.
31 changes: 12 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Unraid Integration for Home Assistant

This custom integration allows you to monitor and control your Unraid server from Home Assistant.
This custom integration allows you to monitor and control your Unraid server from Home Assistant. Unraid is a popular NAS (Network Attached Storage) operating system that provides flexible storage, virtualization, and application support.

## Features

Expand Down Expand Up @@ -28,16 +28,19 @@ During the setup, you'll need to provide:
- Username: Your Unraid username (usually 'root')
- Password: Your Unraid password
- Port: SSH port (usually 22)
- Ping Interval: How often to check if the server is online (in seconds)
- Update Interval: How often to update sensor data (in seconds)
- Ping Interval: How often to check if the server is online (in seconds). Default is 60 seconds.
- Update Interval: How often to update sensor data (in seconds). Default is 300 seconds.

Note: Setting lower intervals will provide more up-to-date information but may increase system load.

## Sensors

- CPU Usage
- RAM Usage
- Array Usage
- Individual Array Disks
- Uptime
- CPU Usage: Shows the current CPU utilization percentage.
- RAM Usage: Displays the current RAM usage percentage.
- Array Usage: Shows the overall array usage percentage.
- Individual Array Disks: Displays usage information for each disk in the array.
- Uptime: Shows how long the Unraid server has been running.
- UPS Status: Displays information about the connected UPS (if available).

## Switches

Expand All @@ -49,14 +52,4 @@ During the setup, you'll need to provide:
- `unraid.execute_command`: Execute a shell command on the Unraid server
- `unraid.execute_in_container`: Execute a command in a Docker container
- `unraid.execute_user_script`: Execute a user script
- `unraid.stop_user_script`: Stop a running user script

## Examples

### Execute a shell command

```yaml
service: unraid.execute_command
data:
entry_id: YOUR_ENTRY_ID
command: "echo 'Hello from Home Assistant' > /boot/config/plugins/user.scripts/scripts/ha_test.sh"
- `unraid.stop_user_script`: Stop a running user script
73 changes: 69 additions & 4 deletions custom_components/unraid/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from datetime import datetime, timedelta
from homeassistant.util import dt as dt_util
from homeassistant.const import UnitOfTemperature

from .const import DOMAIN
from .coordinator import UnraidDataUpdateCoordinator
Expand Down Expand Up @@ -43,10 +44,13 @@ async def async_setup_entry(
UnraidBootUsageSensor(coordinator),
UnraidUptimeSensor(coordinator),
UnraidUPSSensor(coordinator),
UnraidCPUTemperatureSensor(coordinator),
UnraidMotherboardTemperatureSensor(coordinator),
]
# Add individual disk sensors
for disk in coordinator.data["system_stats"].get("individual_disks", []):
sensors.append(UnraidIndividualDiskSensor(coordinator, disk["name"]))
if disk["name"].startswith("disk") and disk["mount_point"].startswith("/mnt/disk"):
sensors.append(UnraidIndividualDiskSensor(coordinator, disk["name"]))

async_add_entities(sensors)

Expand Down Expand Up @@ -198,15 +202,18 @@ def native_unit_of_measurement(self):
@property
def extra_state_attributes(self):
"""Return the state attributes."""
attributes = {}
for disk in self.coordinator.data["system_stats"].get("individual_disks", []):
if disk["name"] == self._disk_name:
return {
attributes = {
"total_size": format_size(disk["total"]),
"used_space": format_size(disk["used"]),
"free_space": format_size(disk["free"]),
"mount_point": disk["mount_point"],
}
return {}
break

return attributes

class UnraidCacheUsageSensor(UnraidSensorBase):
"""Representation of Unraid Cache usage sensor."""
Expand Down Expand Up @@ -351,4 +358,62 @@ def extra_state_attributes(self):
"nominal_power": ups_info.get("NOMPOWER", "Unknown"),
"line_voltage": ups_info.get("LINEV", "Unknown"),
"battery_voltage": ups_info.get("BATTV", "Unknown"),
}
}

class UnraidCPUTemperatureSensor(UnraidSensorBase):
"""Representation of Unraid CPU temperature sensor."""

def __init__(self, coordinator: UnraidDataUpdateCoordinator) -> None:
"""Initialize the sensor."""
super().__init__(
coordinator,
"cpu_temperature",
"CPU Temperature",
"mdi:thermometer",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
)

@property
def native_value(self):
"""Return the state of the sensor."""
temp_data = self.coordinator.data["system_stats"].get("temperature_data", {})
sensors_data = temp_data.get("sensors", {})
for sensor, data in sensors_data.items():
if "Core 0" in data:
return float(data["Core 0"].replace('°C', '').replace(' C', '').replace('+', ''))
return None

@property
def native_unit_of_measurement(self):
"""Return the unit of measurement."""
return UnitOfTemperature.CELSIUS

class UnraidMotherboardTemperatureSensor(UnraidSensorBase):
"""Representation of Unraid motherboard temperature sensor."""

def __init__(self, coordinator: UnraidDataUpdateCoordinator) -> None:
"""Initialize the sensor."""
super().__init__(
coordinator,
"motherboard_temperature",
"Motherboard Temperature",
"mdi:thermometer",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
)

@property
def native_value(self):
"""Return the state of the sensor."""
temp_data = self.coordinator.data["system_stats"].get("temperature_data", {})
sensors_data = temp_data.get("sensors", {})
for sensor, data in sensors_data.items():
if "MB Temp" in data:
return float(data["MB Temp"].replace('°C', '').replace(' C', '').replace('+', ''))
return None

@property
def native_unit_of_measurement(self):
"""Return the unit of measurement."""
return UnitOfTemperature.CELSIUS
2 changes: 1 addition & 1 deletion custom_components/unraid/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def __init__(self, coordinator: UnraidDataUpdateCoordinator, vm_name: str) -> No
self._vm_name = vm_name
self._attr_name = f"Unraid VM {vm_name}"
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_vm_{vm_name}"
self._attr_icon = "mdi:virtual-machine"
self._attr_icon = "mdi:desktop-classic"

@property
def is_on(self) -> bool:
Expand Down
71 changes: 58 additions & 13 deletions custom_components/unraid/unraid.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ async def get_system_stats(self) -> Dict[str, Any]:
boot_usage = await self._get_boot_usage()
uptime = await self._get_uptime()
ups_info = await self.get_ups_info()
temperature_data = await self.get_temperature_data()

return {
"cpu_usage": cpu_usage,
Expand All @@ -59,6 +60,7 @@ async def get_system_stats(self) -> Dict[str, Any]:
"boot_usage": boot_usage,
"uptime": uptime,
"ups_info": ups_info,
"temperature_data": temperature_data,
}

async def _get_cpu_usage(self) -> Optional[float]:
Expand Down Expand Up @@ -126,19 +128,20 @@ async def get_individual_disk_usage(self) -> List[Dict[str, Any]]:
for line in result.stdout.splitlines():
mount_point, total, used, free = line.split()
disk_name = mount_point.split('/')[-1]
total = int(total) * 1024 # Convert to bytes
used = int(used) * 1024 # Convert to bytes
free = int(free) * 1024 # Convert to bytes
percentage = (used / total) * 100 if total > 0 else 0

disks.append({
"name": disk_name,
"mount_point": mount_point,
"percentage": round(percentage, 2),
"total": total,
"used": used,
"free": free
})
if disk_name.startswith('disk'): # Only include actual disks, not tmpfs
total = int(total) * 1024 # Convert to bytes
used = int(used) * 1024 # Convert to bytes
free = int(free) * 1024 # Convert to bytes
percentage = (used / total) * 100 if total > 0 else 0

disks.append({
"name": disk_name,
"mount_point": mount_point,
"percentage": round(percentage, 2),
"total": total,
"used": used,
"free": free
})

return disks
except Exception as e:
Expand Down Expand Up @@ -240,6 +243,48 @@ async def get_ups_info(self) -> Dict[str, Any]:
_LOGGER.error(f"Error getting UPS info: {e}")
return {}

async def get_temperature_data(self):
temp_data = {}

# Get CPU and motherboard temperatures
try:
result = await self.execute_command("sensors")
if result.exit_status == 0:
temp_data['sensors'] = self._parse_sensors_output(result.stdout)
except Exception as e:
_LOGGER.error(f"Error getting sensors data: {e}")

# Get thermal zone temperatures
try:
result = await self.execute_command("paste <(cat /sys/class/thermal/thermal_zone*/type) <(cat /sys/class/thermal/thermal_zone*/temp)")
if result.exit_status == 0:
temp_data['thermal_zones'] = self._parse_thermal_zones(result.stdout)
except Exception as e:
_LOGGER.error(f"Error getting thermal zone data: {e}")

return temp_data

def _parse_sensors_output(self, output):
sensors_data = {}
current_sensor = None
for line in output.splitlines():
if ':' not in line:
current_sensor = line.strip()
sensors_data[current_sensor] = {}
else:
key, value = line.split(':', 1)
key = key.strip()
value = value.split('(')[0].strip()
sensors_data[current_sensor][key] = value
return sensors_data

def _parse_thermal_zones(self, output):
thermal_zones = {}
for line in output.splitlines():
zone_type, temp = line.split()
thermal_zones[zone_type] = float(temp) / 1000 # Convert milli-Celsius to Celsius
return thermal_zones

async def get_docker_containers(self) -> List[Dict[str, Any]]:
try:
result = await self.execute_command("docker ps -a --format '{{.Names}}|{{.State}}'")
Expand Down

0 comments on commit ef77a03

Please sign in to comment.