Files
fn_nas/custom_components/fn_nas/sensor.py
2025-06-30 15:31:15 +08:00

497 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logging
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass, SensorStateClass
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.const import UnitOfTemperature
from .const import (
DOMAIN, HDD_TEMP, HDD_HEALTH, HDD_STATUS, SYSTEM_INFO, ICON_DISK,
ICON_TEMPERATURE, ICON_HEALTH, ATTR_DISK_MODEL, ATTR_SERIAL_NO,
ATTR_POWER_ON_HOURS, ATTR_TOTAL_CAPACITY, ATTR_HEALTH_STATUS,
DEVICE_ID_NAS, DATA_UPDATE_COORDINATOR
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
domain_data = hass.data[DOMAIN][config_entry.entry_id]
coordinator = domain_data[DATA_UPDATE_COORDINATOR]
ups_coordinator = domain_data["ups_coordinator"]
entities = []
existing_ids = set()
# 添加硬盘传感器
for disk in coordinator.data.get("disks", []):
# 温度传感器
temp_uid = f"{config_entry.entry_id}_{disk['device']}_temperature"
if temp_uid not in existing_ids:
entities.append(
DiskSensor(
coordinator,
disk["device"],
HDD_TEMP,
f"硬盘 {disk.get('model', '未知')} 温度",
temp_uid,
UnitOfTemperature.CELSIUS,
ICON_TEMPERATURE,
disk
)
)
existing_ids.add(temp_uid)
# 健康状态传感器
health_uid = f"{config_entry.entry_id}_{disk['device']}_health"
if health_uid not in existing_ids:
entities.append(
DiskSensor(
coordinator,
disk["device"],
HDD_HEALTH,
f"硬盘 {disk.get('model', '未知')} 健康状态",
health_uid,
None,
ICON_HEALTH,
disk
)
)
existing_ids.add(health_uid)
# 硬盘状态传感器
status_uid = f"{config_entry.entry_id}_{disk['device']}_status"
if status_uid not in existing_ids:
entities.append(
DiskSensor(
coordinator,
disk["device"],
HDD_STATUS,
f"硬盘 {disk.get('model', '未知')} 状态",
status_uid,
None,
ICON_DISK,
disk
)
)
existing_ids.add(status_uid)
# 添加系统信息传感器
system_uid = f"{config_entry.entry_id}_system_status"
if system_uid not in existing_ids:
entities.append(
SystemSensor(
coordinator,
"系统状态",
system_uid,
None,
"mdi:server",
)
)
existing_ids.add(system_uid)
# 添加CPU温度传感器
cpu_temp_uid = f"{config_entry.entry_id}_cpu_temperature"
if cpu_temp_uid not in existing_ids:
entities.append(
CPUTempSensor(
coordinator,
"CPU温度",
cpu_temp_uid,
UnitOfTemperature.CELSIUS,
"mdi:thermometer",
)
)
existing_ids.add(cpu_temp_uid)
# 添加主板温度传感器
mobo_temp_uid = f"{config_entry.entry_id}_motherboard_temperature"
if mobo_temp_uid not in existing_ids:
entities.append(
MoboTempSensor(
coordinator,
"主板温度",
mobo_temp_uid,
UnitOfTemperature.CELSIUS,
"mdi:thermometer",
)
)
existing_ids.add(mobo_temp_uid)
# 添加虚拟机状态传感器
if "vms" in coordinator.data:
for vm in coordinator.data["vms"]:
vm_uid = f"{config_entry.entry_id}_flynas_vm_{vm['name']}_status"
if vm_uid not in existing_ids:
entities.append(
VMStatusSensor(
coordinator,
vm["name"],
vm.get("title", vm["name"]),
config_entry.entry_id
)
)
existing_ids.add(vm_uid)
# 添加UPS传感器使用UPS协调器
if ups_coordinator.data: # 检查是否有UPS数据
ups_data = ups_coordinator.data
# UPS电池电量传感器
ups_battery_uid = f"{config_entry.entry_id}_ups_battery"
if ups_battery_uid not in existing_ids:
entities.append(
UPSSensor(
ups_coordinator, # 使用UPS协调器
"UPS电池电量",
ups_battery_uid,
"%",
"mdi:battery",
"battery_level",
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT
)
)
existing_ids.add(ups_battery_uid)
# UPS剩余时间传感器
ups_runtime_uid = f"{config_entry.entry_id}_ups_runtime"
if ups_runtime_uid not in existing_ids:
entities.append(
UPSSensor(
ups_coordinator, # 使用UPS协调器
"UPS剩余时间",
ups_runtime_uid,
"分钟",
"mdi:clock",
"runtime_remaining",
state_class=SensorStateClass.MEASUREMENT
)
)
existing_ids.add(ups_runtime_uid)
# UPS输出电压传感器
ups_output_voltage_uid = f"{config_entry.entry_id}_ups_output_voltage"
if ups_output_voltage_uid not in existing_ids:
entities.append(
UPSSensor(
ups_coordinator, # 使用UPS协调器
"UPS输出电压",
ups_output_voltage_uid,
"V",
"mdi:lightning-bolt-outline",
"output_voltage",
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT
)
)
existing_ids.add(ups_output_voltage_uid)
# UPS负载传感器
ups_load_uid = f"{config_entry.entry_id}_ups_load"
if ups_load_uid not in existing_ids:
entities.append(
UPSSensor(
ups_coordinator, # 使用UPS协调器
"UPS负载",
ups_load_uid,
"%",
"mdi:gauge",
"load_percent",
state_class=SensorStateClass.MEASUREMENT
)
)
existing_ids.add(ups_load_uid)
# UPS型号传感器
ups_model_uid = f"{config_entry.entry_id}_ups_model"
if ups_model_uid not in existing_ids:
entities.append(
UPSSensor(
ups_coordinator, # 使用UPS协调器
"UPS型号",
ups_model_uid,
None,
"mdi:information",
"model"
)
)
existing_ids.add(ups_model_uid)
# UPS状态传感器
ups_status_uid = f"{config_entry.entry_id}_ups_status"
if ups_status_uid not in existing_ids:
entities.append(
UPSSensor(
ups_coordinator, # 使用UPS协调器
"UPS状态",
ups_status_uid,
None,
"mdi:power-plug",
"status"
)
)
existing_ids.add(ups_status_uid)
async_add_entities(entities)
class DiskSensor(CoordinatorEntity, SensorEntity):
def __init__(self, coordinator, device_id, sensor_type, name, unique_id, unit, icon, disk_info):
super().__init__(coordinator)
self.device_id = device_id
self.sensor_type = sensor_type
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_native_unit_of_measurement = unit
self._attr_icon = icon
self.disk_info = disk_info
self._attr_device_info = {
"identifiers": {(DOMAIN, f"disk_{device_id}")},
"name": disk_info.get("model", "未知硬盘"),
"manufacturer": "硬盘设备",
"via_device": (DOMAIN, DEVICE_ID_NAS)
}
@property
def native_value(self):
for disk in self.coordinator.data.get("disks", []):
if disk["device"] == self.device_id:
if self.sensor_type == HDD_TEMP:
temp = disk.get("temperature")
if temp is None or temp == "未知" or temp == "未检测":
return None
if isinstance(temp, str):
try:
if "°C" in temp:
return float(temp.replace("°C", "").strip())
return float(temp)
except ValueError:
return None
elif isinstance(temp, (int, float)):
return temp
return None
elif self.sensor_type == HDD_HEALTH:
health = disk.get("health", "未知")
if health == "未检测":
return "未检测"
return health if health != "未知" else "未知状态"
elif self.sensor_type == HDD_STATUS:
return disk.get("status", "未知")
return None
@property
def device_class(self):
if self.sensor_type == HDD_TEMP:
return SensorDeviceClass.TEMPERATURE
return None
@property
def extra_state_attributes(self):
return {
ATTR_DISK_MODEL: self.disk_info.get("model", "未知"),
ATTR_SERIAL_NO: self.disk_info.get("serial", "未知"),
ATTR_POWER_ON_HOURS: self.disk_info.get("power_on_hours", "未知"),
ATTR_TOTAL_CAPACITY: self.disk_info.get("capacity", "未知"),
ATTR_HEALTH_STATUS: self.disk_info.get("health", "未知"),
"设备ID": self.device_id,
"状态": self.disk_info.get("status", "未知")
}
class SystemSensor(CoordinatorEntity, SensorEntity):
def __init__(self, coordinator, name, unique_id, unit, icon):
super().__init__(coordinator)
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_native_unit_of_measurement = unit
self._attr_icon = icon
self._attr_device_info = {
"identifiers": {(DOMAIN, DEVICE_ID_NAS)},
"name": "飞牛NAS系统监控",
"manufacturer": "飞牛"
}
self._last_uptime = None
@property
def native_value(self):
system_data = self.coordinator.data.get("system", {})
status = system_data.get("status", "unknown")
if status == "off":
return "离线"
if status == "rebooting":
return "重启中"
if status == "unknown":
return "状态未知"
try:
uptime_seconds = system_data.get("uptime_seconds", 0)
if self._last_uptime == uptime_seconds:
return self._last_value
hours = float(uptime_seconds) / 3600
value = f"已运行 {hours:.1f}小时"
self._last_value = value
self._last_uptime = uptime_seconds
return value
except (ValueError, TypeError):
return "运行中"
@property
def extra_state_attributes(self):
system_data = self.coordinator.data.get("system", {})
return {
"运行时间": system_data.get("uptime", "未知"),
"系统状态": system_data.get("status", "unknown"),
"主机地址": self.coordinator.host,
"CPU温度": system_data.get("cpu_temperature", "未知"),
"主板温度": system_data.get("motherboard_temperature", "未知")
}
class CPUTempSensor(CoordinatorEntity, SensorEntity):
def __init__(self, coordinator, name, unique_id, unit, icon):
super().__init__(coordinator)
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_native_unit_of_measurement = unit
self._attr_icon = icon
self._attr_device_info = {
"identifiers": {(DOMAIN, DEVICE_ID_NAS)},
"name": "飞牛NAS系统监控",
"manufacturer": "飞牛"
}
self._attr_device_class = SensorDeviceClass.TEMPERATURE
@property
def native_value(self):
system_data = self.coordinator.data.get("system", {})
temp_str = system_data.get("cpu_temperature", "未知")
if system_data.get("status") == "off":
return None
if temp_str is None or temp_str == "未知":
return None
if isinstance(temp_str, (int, float)):
return temp_str
if "°C" in temp_str:
try:
return float(temp_str.replace("°C", "").strip())
except:
return None
return None
class MoboTempSensor(CoordinatorEntity, SensorEntity):
def __init__(self, coordinator, name, unique_id, unit, icon):
super().__init__(coordinator)
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_native_unit_of_measurement = unit
self._attr_icon = icon
self._attr_device_info = {
"identifiers": {(DOMAIN, DEVICE_ID_NAS)},
"name": "飞牛NAS系统监控",
"manufacturer": "飞牛"
}
self._attr_device_class = SensorDeviceClass.TEMPERATURE
@property
def native_value(self):
system_data = self.coordinator.data.get("system", {})
temp_str = system_data.get("motherboard_temperature", "未知")
if system_data.get("status") == "off":
return None
if temp_str is None or temp_str == "未知":
return None
if isinstance(temp_str, (int, float)):
return temp_str
try:
cleaned = temp_str.lower().replace('°c', '').replace('c', '').strip()
return float(cleaned)
except (ValueError, TypeError) as e:
_LOGGER.warning("主板温度解析失败: 原始值='%s', 错误: %s", temp_str, str(e))
return None
class UPSSensor(CoordinatorEntity, SensorEntity):
def __init__(self, coordinator, name, unique_id, unit, icon, data_key, device_class=None, state_class=None):
super().__init__(coordinator)
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_native_unit_of_measurement = unit
self._attr_icon = icon
self.data_key = data_key
self._attr_device_info = {
"identifiers": {(DOMAIN, "flynas_ups")},
"name": "飞牛NAS UPS",
"manufacturer": "UPS设备",
"via_device": (DOMAIN, DEVICE_ID_NAS)
}
# 设置设备类和状态类(如果提供)
if device_class:
self._attr_device_class = device_class
if state_class:
self._attr_state_class = state_class
@property
def native_value(self):
return self.coordinator.data.get(self.data_key) # 直接使用协调器的数据
@property
def extra_state_attributes(self):
attributes = {
"最后更新时间": self.coordinator.data.get("last_update", "未知"),
"UPS类型": self.coordinator.data.get("ups_type", "未知")
}
# 添加原始字符串值(如果存在)
if f"{self.data_key}_str" in self.coordinator.data:
attributes["原始值"] = self.coordinator.data[f"{self.data_key}_str"]
return attributes
class VMStatusSensor(CoordinatorEntity, SensorEntity):
"""虚拟机状态传感器"""
def __init__(self, coordinator, vm_name, vm_title, entry_id):
super().__init__(coordinator)
self.vm_name = vm_name
self.vm_title = vm_title
self._attr_name = f"{vm_title} 状态"
self._attr_unique_id = f"{entry_id}_flynas_vm_{vm_name}_status" # 使用entry_id确保唯一性
self._attr_device_info = {
"identifiers": {(DOMAIN, f"vm_{vm_name}")},
"name": vm_title,
"via_device": (DOMAIN, DEVICE_ID_NAS)
}
@property
def native_value(self):
"""返回虚拟机状态"""
for vm in self.coordinator.data.get("vms", []):
if vm["name"] == self.vm_name:
# 将状态转换为中文
state_map = {
"running": "运行中",
"shut off": "已关闭",
"paused": "已暂停",
"rebooting": "重启中",
"crashed": "崩溃"
}
return state_map.get(vm["state"], vm["state"])
return "未知"
@property
def icon(self):
"""根据状态返回图标"""
for vm in self.coordinator.data.get("vms", []):
if vm["name"] == self.vm_name:
if vm["state"] == "running":
return "mdi:server"
elif vm["state"] == "shut off":
return "mdi:server-off"
elif vm["state"] == "rebooting":
return "mdi:server-security"
return "mdi:server"