b99450db2f
Mirror the Proxmox auto-sync pattern for the Zigbee2MQTT and Z-Wave JS UI mesh imports. Connection config + MQTT credentials live in .env only, are never persisted to scan_config.json, and are never returned by any API or shown in the UI (single source of truth). - config: ZIGBEE_* / ZWAVE_* env settings; only sync_enabled+interval are persisted, connection/credentials stay env-only - routes: GET/POST /config, POST /sync-now; auto-sync reuses the exact manual _background_*_import + _persist_pending_import path (fresh import when empty, update-in-place when nodes exist, ScanRun trace) - scheduler: zigbee_sync / zwave_sync jobs with live enable + reschedule - frontend: reusable MeshAutoSync section in Settings (Zigbee, Z-Wave) - .env.example: documented both blocks - tests: scheduler jobs, router config/sync-now/auth, credential-never- persisted, SettingsModal sections Manual Zigbee/Z-Wave import behaviour is unchanged. ha-relevant: no
122 lines
3.7 KiB
Python
122 lines
3.7 KiB
Python
"""Pydantic v2 schemas for Zigbee2MQTT import."""
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
|
|
class ZigbeeImportRequest(BaseModel):
|
|
mqtt_host: str = Field(..., description="MQTT broker hostname or IP address")
|
|
mqtt_port: int = Field(1883, ge=1, le=65535, description="MQTT broker port")
|
|
mqtt_username: str | None = Field(None, description="MQTT username (optional)")
|
|
mqtt_password: str | None = Field(None, description="MQTT password (optional)")
|
|
base_topic: str = Field("zigbee2mqtt", description="Zigbee2MQTT base topic")
|
|
mqtt_tls: bool = Field(False, description="Enable TLS (typically port 8883)")
|
|
mqtt_tls_insecure: bool = Field(
|
|
False, description="Skip TLS certificate verification (self-signed only)"
|
|
)
|
|
|
|
@model_validator(mode="after")
|
|
def _insecure_requires_tls(self) -> "ZigbeeImportRequest":
|
|
if self.mqtt_tls_insecure and not self.mqtt_tls:
|
|
raise ValueError("mqtt_tls_insecure requires mqtt_tls=true")
|
|
return self
|
|
|
|
|
|
class ZigbeeTestConnectionRequest(BaseModel):
|
|
mqtt_host: str
|
|
mqtt_port: int = Field(1883, ge=1, le=65535)
|
|
mqtt_username: str | None = None
|
|
mqtt_password: str | None = None
|
|
mqtt_tls: bool = False
|
|
mqtt_tls_insecure: bool = False
|
|
|
|
@model_validator(mode="after")
|
|
def _insecure_requires_tls(self) -> "ZigbeeTestConnectionRequest":
|
|
if self.mqtt_tls_insecure and not self.mqtt_tls:
|
|
raise ValueError("mqtt_tls_insecure requires mqtt_tls=true")
|
|
return self
|
|
|
|
|
|
class ZigbeeDeviceData(BaseModel):
|
|
ieee_address: str
|
|
friendly_name: str
|
|
device_type: str # Coordinator, Router, EndDevice
|
|
model: str | None = None
|
|
vendor: str | None = None
|
|
description: str | None = None
|
|
lqi: int | None = None
|
|
last_seen: str | None = None
|
|
|
|
|
|
class ZigbeeNodeOut(BaseModel):
|
|
"""A homelable-ready node representation of a Zigbee device."""
|
|
|
|
id: str
|
|
label: str
|
|
type: str # zigbee_coordinator | zigbee_router | zigbee_enddevice
|
|
ieee_address: str
|
|
friendly_name: str
|
|
device_type: str
|
|
model: str | None = None
|
|
vendor: str | None = None
|
|
lqi: int | None = None
|
|
parent_id: str | None = None
|
|
|
|
|
|
class ZigbeeEdgeOut(BaseModel):
|
|
source: str
|
|
target: str
|
|
|
|
|
|
class ZigbeeImportResponse(BaseModel):
|
|
nodes: list[ZigbeeNodeOut]
|
|
edges: list[ZigbeeEdgeOut]
|
|
device_count: int
|
|
|
|
|
|
class ZigbeeTestConnectionResponse(BaseModel):
|
|
connected: bool
|
|
message: str
|
|
|
|
|
|
class ZigbeeCoordinatorOut(BaseModel):
|
|
id: str
|
|
label: str
|
|
ieee_address: str
|
|
|
|
|
|
class ZigbeeImportPendingResponse(BaseModel):
|
|
"""Result of importing a Z2M network into the pending section."""
|
|
|
|
pending_created: int
|
|
pending_updated: int
|
|
coordinator: ZigbeeCoordinatorOut | None = None
|
|
coordinator_already_existed: bool = False
|
|
links_recorded: int
|
|
device_count: int
|
|
|
|
|
|
class ZigbeeConfig(BaseModel):
|
|
"""Non-secret Zigbee connection + auto-sync config (GET response).
|
|
|
|
MQTT connection fields (host/port/base_topic/tls) are env-only and
|
|
read-only here — surfaced for display. ``host_configured`` reflects whether
|
|
a server-side MQTT host is set (required for auto-sync). MQTT credentials
|
|
(username/password) are never carried."""
|
|
|
|
mqtt_host: str = ""
|
|
mqtt_port: int = Field(1883, ge=1, le=65535)
|
|
base_topic: str = "zigbee2mqtt"
|
|
mqtt_tls: bool = False
|
|
sync_enabled: bool = False
|
|
sync_interval: int = Field(3600, ge=300)
|
|
host_configured: bool = False
|
|
|
|
|
|
class ZigbeeSyncConfig(BaseModel):
|
|
"""User-editable auto-sync config (POST body). The ONLY persisted Zigbee
|
|
settings. Connection fields (host/port/credentials/topic/tls) are env-only
|
|
and are deliberately not accepted here."""
|
|
|
|
sync_enabled: bool = False
|
|
sync_interval: int = Field(3600, ge=300)
|