667 lines
16 KiB
TypeScript
667 lines
16 KiB
TypeScript
/**
|
|
* 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<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* 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<string, unknown>
|
|
}
|
|
|
|
/**
|
|
* 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<string, unknown>
|
|
}
|
|
|
|
/**
|
|
* 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<string, unknown>
|
|
}
|
|
|
|
/**
|
|
* 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<string, string>;
|
|
/** 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<THomeAssistantDomain, string> = {
|
|
'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;
|