/** * Home Assistant Specific Interfaces * Types for Home Assistant WebSocket API, entities, and configuration */ // ============================================================================ // Configuration // ============================================================================ /** * Configuration for connecting to a Home Assistant instance */ export interface IHomeAssistantInstanceConfig { /** Home Assistant host (IP or hostname) */ host: string; /** Port number (default: 8123) */ port?: number; /** Long-lived access token from HA */ token: string; /** Use secure WebSocket (wss://) */ secure?: boolean; /** Friendly name for this instance */ friendlyName?: string; /** Auto-reconnect on disconnect (default: true) */ autoReconnect?: boolean; /** Reconnect delay in ms (default: 5000) */ reconnectDelay?: number; } /** * Home Assistant configuration in DeviceManager options */ export interface IHomeAssistantOptions { /** Enable mDNS auto-discovery of HA instances */ autoDiscovery?: boolean; /** Manually configured HA instances */ instances?: IHomeAssistantInstanceConfig[]; /** Filter: only discover these domains (default: all) */ enabledDomains?: THomeAssistantDomain[]; /** Auto-reconnect on disconnect (default: true) */ autoReconnect?: boolean; /** Reconnect delay in ms (default: 5000) */ reconnectDelay?: number; } // ============================================================================ // Entity Types // ============================================================================ /** * Supported Home Assistant domains that map to features */ export type THomeAssistantDomain = | 'light' | 'switch' | 'sensor' | 'binary_sensor' | 'climate' | 'fan' | 'cover' | 'lock' | 'camera' | 'media_player'; /** * Home Assistant entity state */ export interface IHomeAssistantEntity { /** Entity ID (e.g., "light.living_room") */ entity_id: string; /** Current state value (e.g., "on", "off", "25.5") */ state: string; /** Additional attributes */ attributes: IHomeAssistantEntityAttributes; /** Last changed timestamp */ last_changed: string; /** Last updated timestamp */ last_updated: string; /** Context information */ context: IHomeAssistantContext; } /** * Common entity attributes */ export interface IHomeAssistantEntityAttributes { /** Friendly name */ friendly_name?: string; /** Device class */ device_class?: string; /** Unit of measurement */ unit_of_measurement?: string; /** Icon */ icon?: string; /** Entity category */ entity_category?: string; /** Assumed state (for optimistic updates) */ assumed_state?: boolean; /** Supported features bitmask */ supported_features?: number; /** Additional dynamic attributes */ [key: string]: unknown; } /** * Light-specific attributes */ export interface IHomeAssistantLightAttributes extends IHomeAssistantEntityAttributes { brightness?: number; // 0-255 color_temp?: number; // Mireds color_temp_kelvin?: number; // Kelvin hs_color?: [number, number]; // [hue 0-360, saturation 0-100] rgb_color?: [number, number, number]; xy_color?: [number, number]; rgbw_color?: [number, number, number, number]; rgbww_color?: [number, number, number, number, number]; effect?: string; effect_list?: string[]; color_mode?: string; supported_color_modes?: string[]; min_mireds?: number; max_mireds?: number; min_color_temp_kelvin?: number; max_color_temp_kelvin?: number; } /** * Climate-specific attributes */ export interface IHomeAssistantClimateAttributes extends IHomeAssistantEntityAttributes { hvac_modes?: string[]; hvac_action?: string; current_temperature?: number; target_temp_high?: number; target_temp_low?: number; temperature?: number; preset_mode?: string; preset_modes?: string[]; fan_mode?: string; fan_modes?: string[]; swing_mode?: string; swing_modes?: string[]; aux_heat?: boolean; current_humidity?: number; humidity?: number; min_temp?: number; max_temp?: number; target_temp_step?: number; min_humidity?: number; max_humidity?: number; } /** * Sensor-specific attributes */ export interface IHomeAssistantSensorAttributes extends IHomeAssistantEntityAttributes { state_class?: 'measurement' | 'total' | 'total_increasing'; native_unit_of_measurement?: string; native_value?: string | number; } /** * Cover-specific attributes */ export interface IHomeAssistantCoverAttributes extends IHomeAssistantEntityAttributes { current_position?: number; // 0-100 current_tilt_position?: number; // 0-100 } /** * Fan-specific attributes */ export interface IHomeAssistantFanAttributes extends IHomeAssistantEntityAttributes { percentage?: number; // 0-100 percentage_step?: number; preset_mode?: string; preset_modes?: string[]; oscillating?: boolean; direction?: 'forward' | 'reverse'; } /** * Lock-specific attributes */ export interface IHomeAssistantLockAttributes extends IHomeAssistantEntityAttributes { is_locked?: boolean; is_locking?: boolean; is_unlocking?: boolean; is_jammed?: boolean; } /** * Camera-specific attributes */ export interface IHomeAssistantCameraAttributes extends IHomeAssistantEntityAttributes { access_token?: string; entity_picture?: string; frontend_stream_type?: 'hls' | 'web_rtc'; is_streaming?: boolean; motion_detection?: boolean; } /** * Media player-specific attributes */ export interface IHomeAssistantMediaPlayerAttributes extends IHomeAssistantEntityAttributes { volume_level?: number; // 0-1 is_volume_muted?: boolean; media_content_id?: string; media_content_type?: string; media_duration?: number; media_position?: number; media_position_updated_at?: string; media_title?: string; media_artist?: string; media_album_name?: string; media_album_artist?: string; media_track?: number; media_series_title?: string; media_season?: number; media_episode?: number; app_id?: string; app_name?: string; source?: string; source_list?: string[]; sound_mode?: string; sound_mode_list?: string[]; shuffle?: boolean; repeat?: 'off' | 'all' | 'one'; entity_picture_local?: string; } /** * Context for entity state changes */ export interface IHomeAssistantContext { id: string; parent_id?: string; user_id?: string; } // ============================================================================ // WebSocket Message Types // ============================================================================ /** * Base message structure */ export interface IHomeAssistantMessage { id?: number; type: string; } /** * Authentication required message */ export interface IHomeAssistantAuthRequired extends IHomeAssistantMessage { type: 'auth_required'; ha_version: string; } /** * Authentication message to send */ export interface IHomeAssistantAuth extends IHomeAssistantMessage { type: 'auth'; access_token: string; } /** * Authentication success */ export interface IHomeAssistantAuthOk extends IHomeAssistantMessage { type: 'auth_ok'; ha_version: string; } /** * Authentication invalid */ export interface IHomeAssistantAuthInvalid extends IHomeAssistantMessage { type: 'auth_invalid'; message: string; } /** * Result message */ export interface IHomeAssistantResult extends IHomeAssistantMessage { id: number; type: 'result'; success: boolean; result?: unknown; error?: { code: string; message: string; }; } /** * Event message */ export interface IHomeAssistantEvent extends IHomeAssistantMessage { id: number; type: 'event'; event: { event_type: string; data: unknown; origin: string; time_fired: string; context: IHomeAssistantContext; }; } /** * State changed event data */ export interface IHomeAssistantStateChangedEvent { entity_id: string; old_state: IHomeAssistantEntity | null; new_state: IHomeAssistantEntity | null; } /** * Subscribe events request */ export interface IHomeAssistantSubscribeEvents extends IHomeAssistantMessage { id: number; type: 'subscribe_events'; event_type?: string; } /** * Get states request */ export interface IHomeAssistantGetStates extends IHomeAssistantMessage { id: number; type: 'get_states'; } /** * Call service request */ export interface IHomeAssistantCallService extends IHomeAssistantMessage { id: number; type: 'call_service'; domain: string; service: string; target?: { entity_id?: string | string[]; device_id?: string | string[]; area_id?: string | string[]; }; service_data?: Record; } /** * Get services request */ export interface IHomeAssistantGetServices extends IHomeAssistantMessage { id: number; type: 'get_services'; } /** * Get config request */ export interface IHomeAssistantGetConfig extends IHomeAssistantMessage { id: number; type: 'get_config'; } /** * Home Assistant config response */ export interface IHomeAssistantConfig { latitude: number; longitude: number; elevation: number; unit_system: { length: string; mass: string; pressure: string; temperature: string; volume: string; }; location_name: string; time_zone: string; components: string[]; config_dir: string; allowlist_external_dirs: string[]; allowlist_external_urls: string[]; version: string; config_source: string; safe_mode: boolean; state: 'NOT_RUNNING' | 'STARTING' | 'RUNNING' | 'STOPPING' | 'FINAL_WRITE'; external_url: string | null; internal_url: string | null; currency: string; country: string; language: string; } // ============================================================================ // Service Definitions // ============================================================================ /** * Light service data */ export interface IHomeAssistantLightServiceData { brightness?: number; // 0-255 brightness_pct?: number; // 0-100 brightness_step?: number; // Step to increase/decrease brightness_step_pct?: number; // Step percentage color_temp?: number; // Mireds color_temp_kelvin?: number; // Kelvin hs_color?: [number, number]; // [hue, saturation] rgb_color?: [number, number, number]; xy_color?: [number, number]; rgbw_color?: [number, number, number, number]; rgbww_color?: [number, number, number, number, number]; color_name?: string; kelvin?: number; effect?: string; transition?: number; // Seconds flash?: 'short' | 'long'; profile?: string; [key: string]: unknown; // Index signature for Record } /** * Climate service data */ export interface IHomeAssistantClimateServiceData { hvac_mode?: string; temperature?: number; target_temp_high?: number; target_temp_low?: number; humidity?: number; fan_mode?: string; swing_mode?: string; preset_mode?: string; aux_heat?: boolean; [key: string]: unknown; // Index signature for Record } /** * Cover service data */ export interface IHomeAssistantCoverServiceData { position?: number; // 0-100 tilt_position?: number; // 0-100 } /** * Fan service data */ export interface IHomeAssistantFanServiceData { percentage?: number; // 0-100 percentage_step?: number; preset_mode?: string; direction?: 'forward' | 'reverse'; oscillating?: boolean; [key: string]: unknown; // Index signature for Record } /** * Media player service data */ export interface IHomeAssistantMediaPlayerServiceData { volume_level?: number; // 0-1 is_volume_muted?: boolean; media_content_id?: string; media_content_type?: string; enqueue?: 'play' | 'next' | 'add' | 'replace'; seek_position?: number; source?: string; sound_mode?: string; shuffle?: boolean; repeat?: 'off' | 'all' | 'one'; } // ============================================================================ // Discovery Types // ============================================================================ /** * Discovered Home Assistant instance via mDNS */ export interface IHomeAssistantDiscoveredInstance { /** Instance ID (derived from host) */ id: string; /** Host address */ host: string; /** Port number */ port: number; /** Base URL */ base_url: string; /** mDNS TXT records */ txtRecords: Record; /** Whether connection requires token */ requires_api_password: boolean; /** Friendly name from mDNS */ friendlyName?: string; } // ============================================================================ // Protocol Events // ============================================================================ /** * Events emitted by HomeAssistantProtocol */ export type THomeAssistantProtocolEvents = { 'connected': () => void; 'disconnected': () => void; 'reconnecting': (attempt: number) => void; 'authenticated': (config: IHomeAssistantConfig) => void; 'auth:failed': (message: string) => void; 'state:changed': (event: IHomeAssistantStateChangedEvent) => void; 'states:loaded': (entities: IHomeAssistantEntity[]) => void; 'error': (error: Error) => void; }; /** * Events emitted by HomeAssistantDiscovery */ export type THomeAssistantDiscoveryEvents = { 'instance:found': (instance: IHomeAssistantDiscoveredInstance) => void; 'instance:lost': (instanceId: string) => void; 'entity:found': (entity: IHomeAssistantEntity) => void; 'entity:updated': (entity: IHomeAssistantEntity) => void; 'entity:removed': (entityId: string) => void; 'error': (error: Error) => void; }; // ============================================================================ // Helper Types // ============================================================================ /** * Extract domain from entity_id */ export function getEntityDomain(entityId: string): THomeAssistantDomain | null { const domain = entityId.split('.')[0]; const validDomains: THomeAssistantDomain[] = [ 'light', 'switch', 'sensor', 'binary_sensor', 'climate', 'fan', 'cover', 'lock', 'camera', 'media_player' ]; return validDomains.includes(domain as THomeAssistantDomain) ? domain as THomeAssistantDomain : null; } /** * Map HA domain to feature type */ export function domainToFeatureType(domain: THomeAssistantDomain): string { const mapping: Record = { 'light': 'light', 'switch': 'switch', 'sensor': 'sensor', 'binary_sensor': 'sensor', 'climate': 'climate', 'fan': 'fan', 'cover': 'cover', 'lock': 'lock', 'camera': 'camera', 'media_player': 'playback', }; return mapping[domain]; } /** * Supported light color modes in HA */ export type THomeAssistantColorMode = | 'unknown' | 'onoff' | 'brightness' | 'color_temp' | 'hs' | 'xy' | 'rgb' | 'rgbw' | 'rgbww' | 'white'; /** * Light supported features bitmask */ export const LIGHT_SUPPORT = { EFFECT: 4, FLASH: 8, TRANSITION: 32, } as const; /** * Climate supported features bitmask */ export const CLIMATE_SUPPORT = { TARGET_TEMPERATURE: 1, TARGET_TEMPERATURE_RANGE: 2, TARGET_HUMIDITY: 4, FAN_MODE: 8, PRESET_MODE: 16, SWING_MODE: 32, AUX_HEAT: 64, } as const; /** * Cover supported features bitmask */ export const COVER_SUPPORT = { OPEN: 1, CLOSE: 2, SET_POSITION: 4, STOP: 8, OPEN_TILT: 16, CLOSE_TILT: 32, STOP_TILT: 64, SET_TILT_POSITION: 128, } as const; /** * Fan supported features bitmask */ export const FAN_SUPPORT = { SET_SPEED: 1, OSCILLATE: 2, DIRECTION: 4, PRESET_MODE: 8, } as const; /** * Lock supported features bitmask */ export const LOCK_SUPPORT = { OPEN: 1, } as const; /** * Media player supported features bitmask */ export const MEDIA_PLAYER_SUPPORT = { PAUSE: 1, SEEK: 2, VOLUME_SET: 4, VOLUME_MUTE: 8, PREVIOUS_TRACK: 16, NEXT_TRACK: 32, TURN_ON: 128, TURN_OFF: 256, PLAY_MEDIA: 512, VOLUME_STEP: 1024, SELECT_SOURCE: 2048, STOP: 4096, CLEAR_PLAYLIST: 8192, PLAY: 16384, SHUFFLE_SET: 32768, SELECT_SOUND_MODE: 65536, BROWSE_MEDIA: 131072, REPEAT_SET: 262144, GROUPING: 524288, } as const;