mirror of
https://github.com/xiaochao99/fn_nas
synced 2025-12-27 07:17:11 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcb46f429c | ||
|
|
4ae0b74e78 | ||
|
|
fd079c4ddf |
@@ -33,7 +33,7 @@
|
|||||||
1. 进入**HACS商店**
|
1. 进入**HACS商店**
|
||||||
2. 添加自定义存储库:
|
2. 添加自定义存储库:
|
||||||
```shell
|
```shell
|
||||||
https://github.com/anxms/fn_nas
|
https://github.com/xiaochao99/fn_nas
|
||||||
```
|
```
|
||||||
3. 搜索"飞牛NAS",点击下载
|
3. 搜索"飞牛NAS",点击下载
|
||||||
4. **重启Home Assistant服务**
|
4. **重启Home Assistant服务**
|
||||||
|
|||||||
72
custom_components/fn_nas/binary_sensor.py
Normal file
72
custom_components/fn_nas/binary_sensor.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import logging
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
from .const import (
|
||||||
|
DOMAIN, HDD_HEALTH, 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]
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
existing_ids = set()
|
||||||
|
|
||||||
|
# 添加硬盘健康状态二元传感器
|
||||||
|
for disk in coordinator.data.get("disks", []):
|
||||||
|
health_uid = f"{config_entry.entry_id}_{disk['device']}_health_binary"
|
||||||
|
if health_uid not in existing_ids:
|
||||||
|
entities.append(
|
||||||
|
DiskHealthBinarySensor(
|
||||||
|
coordinator,
|
||||||
|
disk["device"],
|
||||||
|
f"硬盘 {disk.get('model', '未知')} 健康状态",
|
||||||
|
health_uid,
|
||||||
|
disk
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing_ids.add(health_uid)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class DiskHealthBinarySensor(CoordinatorEntity, BinarySensorEntity):
|
||||||
|
def __init__(self, coordinator, device_id, name, unique_id, disk_info):
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self.device_id = device_id
|
||||||
|
self._attr_name = name
|
||||||
|
self._attr_unique_id = unique_id
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
self._attr_device_class = BinarySensorDeviceClass.PROBLEM
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""返回True表示有问题,False表示正常"""
|
||||||
|
for disk in self.coordinator.data.get("disks", []):
|
||||||
|
if disk["device"] == self.device_id:
|
||||||
|
health = disk.get("health", "未知")
|
||||||
|
# 将健康状态映射为二元状态
|
||||||
|
if health in ["正常", "良好", "OK", "ok", "good", "Good"]:
|
||||||
|
return False # 正常状态
|
||||||
|
elif health in ["警告", "异常", "错误", "warning", "Warning", "error", "Error", "bad", "Bad"]:
|
||||||
|
return True # 有问题状态
|
||||||
|
else:
|
||||||
|
# 未知状态也视为有问题
|
||||||
|
return True
|
||||||
|
return True # 默认视为有问题
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""根据状态返回图标"""
|
||||||
|
if self.is_on:
|
||||||
|
return "mdi:alert-circle" # 有问题时显示警告图标
|
||||||
|
else:
|
||||||
|
return "mdi:check-circle" # 正常时显示对勾图标
|
||||||
@@ -3,6 +3,7 @@ from homeassistant.const import Platform
|
|||||||
DOMAIN = "fn_nas"
|
DOMAIN = "fn_nas"
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
|
Platform.BINARY_SENSOR,
|
||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
Platform.BUTTON
|
Platform.BUTTON
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -92,11 +92,14 @@ class DiskManager:
|
|||||||
self.logger.debug(f"格式化容量失败: {capacity_str}, 错误: {e}")
|
self.logger.debug(f"格式化容量失败: {capacity_str}, 错误: {e}")
|
||||||
return capacity_str
|
return capacity_str
|
||||||
|
|
||||||
async def check_disk_active(self, device: str, window: int = 30) -> bool:
|
async def check_disk_active(self, device: str, window: int = 30, current_status: str = None) -> bool:
|
||||||
"""检查硬盘在指定时间窗口内是否有活动"""
|
"""检查硬盘在指定时间窗口内是否有活动"""
|
||||||
try:
|
try:
|
||||||
# 首先检查硬盘当前状态
|
# 首先检查硬盘当前状态
|
||||||
current_status = await self.get_disk_activity(device)
|
if current_status is None:
|
||||||
|
current_status = await self.get_disk_activity(device)
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"使用传入的状态: {device} = {current_status}")
|
||||||
|
|
||||||
# 如果硬盘处于休眠状态,直接返回非活跃
|
# 如果硬盘处于休眠状态,直接返回非活跃
|
||||||
if current_status == "休眠中":
|
if current_status == "休眠中":
|
||||||
@@ -138,8 +141,8 @@ class DiskManager:
|
|||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 如果硬盘空闲且没有近期活动,返回非活跃
|
# 如果硬盘空闲且没有近期活动,使用缓存信息
|
||||||
self.logger.debug(f"硬盘 {device} 处于空闲状态且无近期活动,不执行详细检测")
|
self.logger.debug(f"硬盘 {device} 处于空闲状态且无近期活动,使用缓存信息")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 如果硬盘处于活动中,返回活跃状态
|
# 如果硬盘处于活动中,返回活跃状态
|
||||||
@@ -186,9 +189,10 @@ class DiskManager:
|
|||||||
async def get_disk_activity(self, device: str) -> str:
|
async def get_disk_activity(self, device: str) -> str:
|
||||||
"""获取硬盘活动状态(活动中/空闲中/休眠中)"""
|
"""获取硬盘活动状态(活动中/空闲中/休眠中)"""
|
||||||
try:
|
try:
|
||||||
# 先检查电源状态
|
# 先检查电源状态 - 这是最可靠的休眠检测方法
|
||||||
power_state = await self.get_disk_power_state(device)
|
power_state = await self.get_disk_power_state(device)
|
||||||
if power_state in ["standby", "sleep"]:
|
if power_state in ["standby", "sleep"]:
|
||||||
|
self.logger.debug(f"硬盘 {device} 电源状态为 {power_state},判定为休眠中")
|
||||||
return "休眠中"
|
return "休眠中"
|
||||||
|
|
||||||
# 检查最近的I/O活动 - 使用非侵入性方式
|
# 检查最近的I/O活动 - 使用非侵入性方式
|
||||||
@@ -256,9 +260,19 @@ class DiskManager:
|
|||||||
self.logger.debug(f"解析硬盘 {device} 统计信息失败: {e}")
|
self.logger.debug(f"解析硬盘 {device} 统计信息失败: {e}")
|
||||||
return "活动中" # 出错时默认返回活动中,避免中断休眠
|
return "活动中" # 出错时默认返回活动中,避免中断休眠
|
||||||
|
|
||||||
# 如果无法获取统计信息,默认返回活动中
|
# 如果无法获取统计信息,检查硬盘是否可访问
|
||||||
self.logger.debug(f"无法获取硬盘 {device} 的统计信息,默认返回活动中")
|
try:
|
||||||
return "活动中"
|
# 尝试读取设备信息,如果成功说明硬盘可访问
|
||||||
|
test_output = await self.coordinator.run_command(f"ls -la /dev/{device} 2>/dev/null")
|
||||||
|
if test_output and device in test_output:
|
||||||
|
self.logger.debug(f"硬盘 {device} 可访问但无统计信息,默认返回活动中")
|
||||||
|
return "活动中"
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"硬盘 {device} 不可访问,可能处于休眠状态")
|
||||||
|
return "休眠中"
|
||||||
|
except:
|
||||||
|
self.logger.debug(f"硬盘 {device} 检测失败,默认返回活动中")
|
||||||
|
return "活动中"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"获取硬盘 {device} 状态失败: {str(e)}", exc_info=True)
|
self.logger.error(f"获取硬盘 {device} 状态失败: {str(e)}", exc_info=True)
|
||||||
@@ -331,31 +345,31 @@ class DiskManager:
|
|||||||
disks.append(disk_info)
|
disks.append(disk_info)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 检查硬盘是否活跃
|
# 检查硬盘是否活跃,传入当前状态确保一致性
|
||||||
is_active = await self.check_disk_active(device, window=30)
|
is_active = await self.check_disk_active(device, window=30, current_status=status)
|
||||||
if not is_active:
|
if not is_active:
|
||||||
self.logger.debug(f"硬盘 {device} 处于非活跃状态,使用上一次获取的信息")
|
self.logger.debug(f"硬盘 {device} 处于非活跃状态,使用上一次获取的信息")
|
||||||
|
|
||||||
# 优先使用缓存的完整信息
|
# 优先使用缓存的完整信息
|
||||||
if cached_info:
|
if cached_info:
|
||||||
disk_info.update({
|
disk_info.update({
|
||||||
"model": cached_info.get("model", "未检测"),
|
"model": cached_info.get("model", "未知"),
|
||||||
"serial": cached_info.get("serial", "未检测"),
|
"serial": cached_info.get("serial", "未知"),
|
||||||
"capacity": cached_info.get("capacity", "未检测"),
|
"capacity": cached_info.get("capacity", "未知"),
|
||||||
"health": cached_info.get("health", "未检测"),
|
"health": cached_info.get("health", "未知"),
|
||||||
"temperature": cached_info.get("temperature", "未检测"),
|
"temperature": cached_info.get("temperature", "未知"),
|
||||||
"power_on_hours": cached_info.get("power_on_hours", "未检测"),
|
"power_on_hours": cached_info.get("power_on_hours", "未知"),
|
||||||
"attributes": cached_info.get("attributes", {})
|
"attributes": cached_info.get("attributes", {})
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# 如果没有缓存信息,使用默认值
|
# 如果没有缓存信息,使用默认值
|
||||||
disk_info.update({
|
disk_info.update({
|
||||||
"model": "未检测",
|
"model": "未知",
|
||||||
"serial": "未检测",
|
"serial": "未知",
|
||||||
"capacity": "未检测",
|
"capacity": "未知",
|
||||||
"health": "未检测",
|
"health": "未知",
|
||||||
"temperature": "未检测",
|
"temperature": "未知",
|
||||||
"power_on_hours": "未检测",
|
"power_on_hours": "未知",
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "fn_nas",
|
"domain": "fn_nas",
|
||||||
"name": "飞牛NAS",
|
"name": "飞牛NAS",
|
||||||
"version": "1.3.8",
|
"version": "1.3.9",
|
||||||
"documentation": "https://github.com/xiaochao99/fn_nas",
|
"documentation": "https://github.com/xiaochao99/fn_nas",
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@xiaochao99"],
|
"codeowners": ["@xiaochao99"],
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ from homeassistant.components.sensor import SensorEntity, SensorDeviceClass, Sen
|
|||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.const import UnitOfTemperature
|
from homeassistant.const import UnitOfTemperature
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN, HDD_TEMP, HDD_HEALTH, HDD_STATUS, SYSTEM_INFO, ICON_DISK,
|
DOMAIN, HDD_TEMP, HDD_STATUS, SYSTEM_INFO, ICON_DISK,
|
||||||
ICON_TEMPERATURE, ICON_HEALTH, ATTR_DISK_MODEL, ATTR_SERIAL_NO,
|
ICON_TEMPERATURE, ATTR_DISK_MODEL, ATTR_SERIAL_NO,
|
||||||
ATTR_POWER_ON_HOURS, ATTR_TOTAL_CAPACITY, ATTR_HEALTH_STATUS,
|
ATTR_POWER_ON_HOURS, ATTR_TOTAL_CAPACITY, ATTR_HEALTH_STATUS,
|
||||||
DEVICE_ID_NAS, DATA_UPDATE_COORDINATOR
|
DEVICE_ID_NAS, DATA_UPDATE_COORDINATOR
|
||||||
)
|
)
|
||||||
@@ -38,22 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
)
|
)
|
||||||
existing_ids.add(temp_uid)
|
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"
|
status_uid = f"{config_entry.entry_id}_{disk['device']}_status"
|
||||||
@@ -302,7 +287,7 @@ class DiskSensor(CoordinatorEntity, SensorEntity):
|
|||||||
if disk["device"] == self.device_id:
|
if disk["device"] == self.device_id:
|
||||||
if self.sensor_type == HDD_TEMP:
|
if self.sensor_type == HDD_TEMP:
|
||||||
temp = disk.get("temperature")
|
temp = disk.get("temperature")
|
||||||
if temp is None or temp == "未知" or temp == "未检测":
|
if temp is None or temp == "未知":
|
||||||
return None
|
return None
|
||||||
if isinstance(temp, str):
|
if isinstance(temp, str):
|
||||||
try:
|
try:
|
||||||
@@ -314,11 +299,7 @@ class DiskSensor(CoordinatorEntity, SensorEntity):
|
|||||||
elif isinstance(temp, (int, float)):
|
elif isinstance(temp, (int, float)):
|
||||||
return temp
|
return temp
|
||||||
return None
|
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:
|
elif self.sensor_type == HDD_STATUS:
|
||||||
return disk.get("status", "未知")
|
return disk.get("status", "未知")
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user