chasm_cli/integrations/
smart_home.rs

1// Copyright (c) 2024-2026 Nervosys LLC
2// SPDX-License-Identifier: Apache-2.0
3//! Smart Home Integrations
4//!
5//! Home Assistant, HomeKit, Hue, SmartThings, IoT devices
6
7use super::IntegrationResult;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11// =============================================================================
12// Common Types
13// =============================================================================
14
15/// Device state
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct DeviceState {
18    pub device_id: String,
19    pub name: String,
20    pub device_type: DeviceType,
21    pub state: String,
22    pub attributes: HashMap<String, serde_json::Value>,
23    pub last_changed: String,
24    pub is_online: bool,
25}
26
27/// Device types
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum DeviceType {
31    Light,
32    Switch,
33    Outlet,
34    Thermostat,
35    Lock,
36    Sensor,
37    Camera,
38    Speaker,
39    Fan,
40    Blind,
41    Garage,
42    Vacuum,
43    AirPurifier,
44    Humidifier,
45    MediaPlayer,
46    Climate,
47    Other,
48}
49
50/// Scene/Automation
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Scene {
53    pub id: String,
54    pub name: String,
55    pub description: Option<String>,
56    pub actions: Vec<DeviceAction>,
57    pub icon: Option<String>,
58}
59
60/// Device action
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct DeviceAction {
63    pub device_id: String,
64    pub action: String,
65    pub parameters: Option<HashMap<String, serde_json::Value>>,
66}
67
68// =============================================================================
69// Home Assistant
70// =============================================================================
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct HomeAssistantConfig {
74    pub url: String,
75    pub access_token: String,
76}
77
78/// Home Assistant entity
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct HAEntity {
81    pub entity_id: String,
82    pub state: String,
83    pub attributes: HashMap<String, serde_json::Value>,
84    pub last_changed: String,
85    pub last_updated: String,
86}
87
88/// Home Assistant service
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct HAService {
91    pub domain: String,
92    pub service: String,
93    pub description: Option<String>,
94    pub fields: HashMap<String, HAServiceField>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct HAServiceField {
99    pub description: String,
100    pub required: bool,
101    pub example: Option<serde_json::Value>,
102}
103
104/// Home Assistant automation
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct HAAutomation {
107    pub id: String,
108    pub alias: String,
109    pub description: Option<String>,
110    pub trigger: Vec<serde_json::Value>,
111    pub condition: Vec<serde_json::Value>,
112    pub action: Vec<serde_json::Value>,
113    pub mode: String,
114}
115
116/// Home Assistant provider trait
117#[async_trait::async_trait]
118pub trait HomeAssistantProvider: Send + Sync {
119    // States
120    async fn list_entities(&self, domain: Option<&str>) -> IntegrationResult;
121    async fn get_state(&self, entity_id: &str) -> IntegrationResult;
122    async fn get_history(&self, entity_id: &str, start: &str, end: &str) -> IntegrationResult;
123
124    // Services
125    async fn list_services(&self) -> IntegrationResult;
126    async fn call_service(
127        &self,
128        domain: &str,
129        service: &str,
130        data: Option<serde_json::Value>,
131    ) -> IntegrationResult;
132
133    // Automation
134    async fn list_automations(&self) -> IntegrationResult;
135    async fn trigger_automation(&self, automation_id: &str) -> IntegrationResult;
136    async fn toggle_automation(&self, automation_id: &str, enabled: bool) -> IntegrationResult;
137
138    // Scenes
139    async fn list_scenes(&self) -> IntegrationResult;
140    async fn activate_scene(&self, scene_id: &str) -> IntegrationResult;
141
142    // Scripts
143    async fn run_script(
144        &self,
145        script_id: &str,
146        data: Option<serde_json::Value>,
147    ) -> IntegrationResult;
148
149    // Events
150    async fn fire_event(
151        &self,
152        event_type: &str,
153        data: Option<serde_json::Value>,
154    ) -> IntegrationResult;
155
156    // Companion app
157    async fn notify(
158        &self,
159        message: &str,
160        title: Option<&str>,
161        data: Option<serde_json::Value>,
162    ) -> IntegrationResult;
163}
164
165// =============================================================================
166// Apple HomeKit
167// =============================================================================
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct HomeKitAccessory {
171    pub id: String,
172    pub name: String,
173    pub room: Option<String>,
174    pub accessory_type: String,
175    pub services: Vec<HomeKitService>,
176    pub is_reachable: bool,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct HomeKitService {
181    pub service_type: String,
182    pub characteristics: Vec<HomeKitCharacteristic>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct HomeKitCharacteristic {
187    pub characteristic_type: String,
188    pub value: serde_json::Value,
189    pub is_readable: bool,
190    pub is_writable: bool,
191}
192
193/// HomeKit provider trait
194#[async_trait::async_trait]
195pub trait HomeKitProvider: Send + Sync {
196    async fn list_homes(&self) -> IntegrationResult;
197    async fn list_rooms(&self, home_id: &str) -> IntegrationResult;
198    async fn list_accessories(&self, home_id: &str) -> IntegrationResult;
199    async fn get_accessory(&self, accessory_id: &str) -> IntegrationResult;
200    async fn set_characteristic(
201        &self,
202        accessory_id: &str,
203        service: &str,
204        characteristic: &str,
205        value: serde_json::Value,
206    ) -> IntegrationResult;
207    async fn list_scenes(&self, home_id: &str) -> IntegrationResult;
208    async fn run_scene(&self, scene_id: &str) -> IntegrationResult;
209}
210
211// =============================================================================
212// Philips Hue
213// =============================================================================
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct HueConfig {
217    pub bridge_ip: String,
218    pub username: String,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct HueLight {
223    pub id: String,
224    pub name: String,
225    pub is_on: bool,
226    pub brightness: u8,          // 1-254
227    pub hue: Option<u16>,        // 0-65535
228    pub saturation: Option<u8>,  // 0-254
229    pub color_temp: Option<u16>, // 153-500 (mirek)
230    pub is_reachable: bool,
231    pub light_type: String,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct HueGroup {
236    pub id: String,
237    pub name: String,
238    pub lights: Vec<String>,
239    pub group_type: String,
240    pub is_on: bool,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct HueScene {
245    pub id: String,
246    pub name: String,
247    pub group: Option<String>,
248    pub lights: Vec<String>,
249}
250
251/// Hue provider trait
252#[async_trait::async_trait]
253pub trait HueProvider: Send + Sync {
254    // Lights
255    async fn list_lights(&self) -> IntegrationResult;
256    async fn get_light(&self, light_id: &str) -> IntegrationResult;
257    async fn set_light_state(
258        &self,
259        light_id: &str,
260        on: Option<bool>,
261        brightness: Option<u8>,
262        color: Option<(u16, u8)>,
263    ) -> IntegrationResult;
264
265    // Groups
266    async fn list_groups(&self) -> IntegrationResult;
267    async fn set_group_state(
268        &self,
269        group_id: &str,
270        on: Option<bool>,
271        brightness: Option<u8>,
272    ) -> IntegrationResult;
273
274    // Scenes
275    async fn list_scenes(&self) -> IntegrationResult;
276    async fn activate_scene(&self, scene_id: &str) -> IntegrationResult;
277
278    // Effects
279    async fn set_color_loop(&self, light_id: &str, enabled: bool) -> IntegrationResult;
280    async fn alert(&self, light_id: &str) -> IntegrationResult;
281}
282
283// =============================================================================
284// Thermostats (Nest, Ecobee)
285// =============================================================================
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct ThermostatState {
289    pub id: String,
290    pub name: String,
291    pub current_temperature: f32,
292    pub target_temperature: Option<f32>,
293    pub target_temperature_low: Option<f32>,
294    pub target_temperature_high: Option<f32>,
295    pub mode: ThermostatMode,
296    pub humidity: Option<u8>,
297    pub is_running: bool,
298    pub fan_mode: Option<FanMode>,
299}
300
301#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
302#[serde(rename_all = "snake_case")]
303pub enum ThermostatMode {
304    Off,
305    Heat,
306    Cool,
307    HeatCool,
308    Auto,
309    Eco,
310}
311
312#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
313#[serde(rename_all = "snake_case")]
314pub enum FanMode {
315    Auto,
316    On,
317    Circulate,
318}
319
320/// Thermostat provider trait
321#[async_trait::async_trait]
322pub trait ThermostatProvider: Send + Sync {
323    async fn get_state(&self, thermostat_id: &str) -> IntegrationResult;
324    async fn set_temperature(&self, thermostat_id: &str, temperature: f32) -> IntegrationResult;
325    async fn set_mode(&self, thermostat_id: &str, mode: ThermostatMode) -> IntegrationResult;
326    async fn set_fan_mode(&self, thermostat_id: &str, fan_mode: FanMode) -> IntegrationResult;
327    async fn set_schedule(
328        &self,
329        thermostat_id: &str,
330        schedule: serde_json::Value,
331    ) -> IntegrationResult;
332}
333
334// =============================================================================
335// Locks (August, Schlage)
336// =============================================================================
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct LockState {
340    pub id: String,
341    pub name: String,
342    pub is_locked: bool,
343    pub is_jammed: bool,
344    pub battery_level: Option<u8>,
345    pub last_activity: Option<String>,
346}
347
348/// Lock provider trait
349#[async_trait::async_trait]
350pub trait LockProvider: Send + Sync {
351    async fn get_state(&self, lock_id: &str) -> IntegrationResult;
352    async fn lock(&self, lock_id: &str) -> IntegrationResult;
353    async fn unlock(&self, lock_id: &str) -> IntegrationResult;
354    async fn get_activity(&self, lock_id: &str, limit: u32) -> IntegrationResult;
355}
356
357// =============================================================================
358// Cameras (Ring, Nest, Wyze)
359// =============================================================================
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct Camera {
363    pub id: String,
364    pub name: String,
365    pub is_online: bool,
366    pub is_recording: bool,
367    pub has_motion: bool,
368    pub battery_level: Option<u8>,
369    pub stream_url: Option<String>,
370}
371
372/// Camera provider trait
373#[async_trait::async_trait]
374pub trait CameraProvider: Send + Sync {
375    async fn list_cameras(&self) -> IntegrationResult;
376    async fn get_camera(&self, camera_id: &str) -> IntegrationResult;
377    async fn get_stream_url(&self, camera_id: &str) -> IntegrationResult;
378    async fn get_snapshot(&self, camera_id: &str) -> IntegrationResult;
379    async fn get_events(&self, camera_id: &str, limit: u32) -> IntegrationResult;
380}
381
382// =============================================================================
383// Sensors
384// =============================================================================
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct Sensor {
388    pub id: String,
389    pub name: String,
390    pub sensor_type: SensorType,
391    pub value: serde_json::Value,
392    pub unit: Option<String>,
393    pub battery_level: Option<u8>,
394    pub last_updated: String,
395}
396
397#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
398#[serde(rename_all = "snake_case")]
399pub enum SensorType {
400    Temperature,
401    Humidity,
402    Motion,
403    Contact,
404    Light,
405    Smoke,
406    CarbonMonoxide,
407    Water,
408    Vibration,
409    Pressure,
410    AirQuality,
411    Power,
412    Energy,
413}
414
415/// Sensor provider trait
416#[async_trait::async_trait]
417pub trait SensorProvider: Send + Sync {
418    async fn list_sensors(&self) -> IntegrationResult;
419    async fn get_sensor(&self, sensor_id: &str) -> IntegrationResult;
420    async fn get_history(&self, sensor_id: &str, start: &str, end: &str) -> IntegrationResult;
421}