Add per-enclosure temperature metrics + Home Assistant MQTT publisher
- enclosure: fetch SES element descriptor page (0x07) and label temp/fan/ psu/voltage elements with human-readable names - temps service + /api/temps: per-enclosure hotspot (max across SES sensors and housed drive temps) plus named SES sensor list - mqtt_publisher: paho-mqtt with HA discovery (device per enclosure, hotspot + per-sensor entities), LWT availability, opt-in via MQTT_HOST - secrets: OpenBao KV v2 reader; MQTT creds sourced from secret/home_assistant with env fallback - compose/requirements/README updated
This commit is contained in:
30
main.py
30
main.py
@@ -10,9 +10,10 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from models.schemas import HealthCheck
|
||||
from routers import drives, enclosures, leds, overview
|
||||
from routers import drives, enclosures, leds, overview, temps
|
||||
from services.cache import cache_set, close_cache, init_cache, redis_available
|
||||
from services.enclosure import discover_enclosures, list_slots
|
||||
from services.mqtt_publisher import MqttPublisher, _PAHO_AVAILABLE, mqtt_enabled
|
||||
from services.smart import SMART_CACHE_TTL, _run_smartctl, sg_ses_available, smartctl_available
|
||||
from services.zfs import get_zfs_pool_map
|
||||
|
||||
@@ -26,6 +27,8 @@ SMART_POLL_INTERVAL = int(os.environ.get("SMART_POLL_INTERVAL", "90"))
|
||||
|
||||
_tool_status: dict[str, bool] = {}
|
||||
_poll_task: asyncio.Task | None = None
|
||||
_mqtt_task: asyncio.Task | None = None
|
||||
_mqtt_publisher: MqttPublisher | None = None
|
||||
|
||||
|
||||
async def smart_poll_loop():
|
||||
@@ -77,7 +80,7 @@ async def smart_poll_loop():
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
global _poll_task
|
||||
global _poll_task, _mqtt_task, _mqtt_publisher
|
||||
# Startup
|
||||
_tool_status["smartctl"] = smartctl_available()
|
||||
_tool_status["sg_ses"] = sg_ses_available()
|
||||
@@ -95,9 +98,31 @@ async def lifespan(app: FastAPI):
|
||||
if redis_available():
|
||||
_poll_task = asyncio.create_task(smart_poll_loop())
|
||||
|
||||
# Optional MQTT publisher for Home Assistant (opt-in via MQTT_HOST).
|
||||
_tool_status["mqtt"] = False
|
||||
if mqtt_enabled():
|
||||
if not _PAHO_AVAILABLE:
|
||||
logger.warning("MQTT_HOST set but paho-mqtt not installed — MQTT disabled")
|
||||
else:
|
||||
try:
|
||||
_mqtt_publisher = MqttPublisher()
|
||||
_mqtt_publisher.start()
|
||||
_mqtt_task = asyncio.create_task(_mqtt_publisher.run())
|
||||
_tool_status["mqtt"] = True
|
||||
except Exception as e:
|
||||
logger.error("Failed to start MQTT publisher: %s", e)
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown
|
||||
if _mqtt_task is not None:
|
||||
_mqtt_task.cancel()
|
||||
try:
|
||||
await _mqtt_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
if _mqtt_publisher is not None:
|
||||
_mqtt_publisher.stop()
|
||||
if _poll_task is not None:
|
||||
_poll_task.cancel()
|
||||
try:
|
||||
@@ -125,6 +150,7 @@ app.include_router(enclosures.router)
|
||||
app.include_router(drives.router)
|
||||
app.include_router(leds.router)
|
||||
app.include_router(overview.router)
|
||||
app.include_router(temps.router)
|
||||
|
||||
|
||||
@app.get("/api/health", response_model=HealthCheck, tags=["health"])
|
||||
|
||||
Reference in New Issue
Block a user