Files
homelable/backend/app/schemas/zwave.py
T
Pouzor 8faf5c1c79 feat: add Z-Wave network scan via MQTT gateway
Import a Z-Wave JS UI (zwavejs2mqtt) network over the MQTT gateway API,
mirroring the existing Zigbee pipeline:

- New Z-Wave Import modal + sidebar entry (broker, prefix, gateway name)
- coordinator/router/end-device typing with mesh tree from node neighbors
- import to Pending section or straight to canvas
- Pending Devices gains a Z-Wave source filter
- shared mqtt_common helpers extracted from the zigbee service

ha-relevant: yes
2026-06-26 11:19:37 +02:00

86 lines
2.6 KiB
Python

"""Pydantic v2 schemas for Z-Wave JS UI (zwavejs2mqtt) import."""
from pydantic import BaseModel, Field, model_validator
class ZwaveImportRequest(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)")
prefix: str = Field("zwave", description="Z-Wave JS UI MQTT prefix")
gateway_name: str = Field("zwavejs2mqtt", description="Z-Wave JS UI gateway name")
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) -> "ZwaveImportRequest":
if self.mqtt_tls_insecure and not self.mqtt_tls:
raise ValueError("mqtt_tls_insecure requires mqtt_tls=true")
return self
class ZwaveTestConnectionRequest(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) -> "ZwaveTestConnectionRequest":
if self.mqtt_tls_insecure and not self.mqtt_tls:
raise ValueError("mqtt_tls_insecure requires mqtt_tls=true")
return self
class ZwaveNodeOut(BaseModel):
"""A homelable-ready node representation of a Z-Wave device."""
id: str
label: str
type: str # zwave_coordinator | zwave_router | zwave_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 ZwaveEdgeOut(BaseModel):
source: str
target: str
class ZwaveImportResponse(BaseModel):
nodes: list[ZwaveNodeOut]
edges: list[ZwaveEdgeOut]
device_count: int
class ZwaveTestConnectionResponse(BaseModel):
connected: bool
message: str
class ZwaveCoordinatorOut(BaseModel):
id: str
label: str
ieee_address: str
class ZwaveImportPendingResponse(BaseModel):
"""Result of importing a Z-Wave network into the pending section."""
pending_created: int
pending_updated: int
coordinator: ZwaveCoordinatorOut | None = None
coordinator_already_existed: bool = False
links_recorded: int
device_count: int