"""Contact Sensor Capability - Fensterkontakt (read-only). This module defines the ContactState model for door/window contact sensors. These sensors report their open/closed state and are read-only devices. Capability Version: contact_sensor@1.0.0 """ from datetime import datetime from typing import Annotated, Literal from pydantic import BaseModel, Field, field_validator # Capability metadata CAP_VERSION = "contact_sensor@1.0.0" DISPLAY_NAME = "Contact Sensor" class ContactState(BaseModel): """State model for contact sensors (door/window sensors). Contact sensors are read-only devices that report whether a door or window is open or closed. They typically also report battery level and signal quality. Attributes: contact: Current state of the contact ("open" or "closed") battery: Battery level percentage (0-100), optional linkquality: MQTT link quality indicator, optional device_temperature: Internal device temperature in °C, optional voltage: Battery voltage in mV, optional ts: Timestamp of the state reading, optional Examples: >>> ContactState(contact="open") ContactState(contact='open', battery=None, ...) >>> ContactState(contact="closed", battery=95, linkquality=87) ContactState(contact='closed', battery=95, linkquality=87, ...) """ contact: Literal["open", "closed"] = Field( ..., description="Contact state: 'open' for open door/window, 'closed' for closed" ) battery: Annotated[int, Field(ge=0, le=100)] | None = Field( None, description="Battery level in percent (0-100)" ) linkquality: int | None = Field( None, description="Link quality indicator (typically 0-255)" ) device_temperature: float | None = Field( None, description="Internal device temperature in degrees Celsius" ) voltage: int | None = Field( None, description="Battery voltage in millivolts" ) ts: datetime | None = Field( None, description="Timestamp of the state reading" ) @staticmethod def normalize_bool(is_open: bool) -> "ContactState": """Convert boolean to ContactState. Helper method to convert a boolean value to a ContactState instance. Useful when integrating with systems that use True/False for contact state. Args: is_open: True if contact is open, False if closed Returns: ContactState instance with appropriate contact value Examples: >>> ContactState.normalize_bool(True) ContactState(contact='open', ...) >>> ContactState.normalize_bool(False) ContactState(contact='closed', ...) """ return ContactState(contact="open" if is_open else "closed") # Public API __all__ = ["ContactState", "CAP_VERSION", "DISPLAY_NAME"]