Skip to content

Commit 4740f8a

Browse files
authored
Merge pull request #133 from duvholt/wake-up-sunrise-hue-crate
Hue crate changes needed for implementing wake-up automation
2 parents e24c18f + a97fd39 commit 4740f8a

10 files changed

Lines changed: 346 additions & 55 deletions

File tree

crates/hue/src/api/behavior.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
use std::ops::AddAssign;
2+
3+
use serde::{Deserialize, Deserializer, Serialize};
4+
use serde_json::Value;
5+
use uuid::{Uuid, uuid};
6+
7+
use super::{DollarRef, ResourceLink};
8+
9+
#[derive(Debug, Serialize, Deserialize, Clone)]
10+
pub struct BehaviorScript {
11+
pub configuration_schema: DollarRef,
12+
pub description: String,
13+
#[serde(skip_serializing_if = "Option::is_none")]
14+
pub max_number_instances: Option<u32>,
15+
pub metadata: BehaviorScriptMetadata,
16+
pub state_schema: DollarRef,
17+
pub supported_features: Vec<String>,
18+
pub trigger_schema: DollarRef,
19+
pub version: String,
20+
}
21+
22+
impl BehaviorScript {
23+
pub const WAKE_UP_ID: Uuid = uuid!("ff8957e3-2eb9-4699-a0c8-ad2cb3ede704");
24+
25+
#[must_use]
26+
pub fn wake_up() -> Self {
27+
Self {
28+
configuration_schema: DollarRef {
29+
dref: Some("basic_wake_up_config.json#".to_string()),
30+
},
31+
description:
32+
"Get your body in the mood to wake up by fading on the lights in the morning."
33+
.to_string(),
34+
max_number_instances: None,
35+
metadata: BehaviorScriptMetadata {
36+
name: "Basic wake up routine".to_string(),
37+
category: "automation".to_string(),
38+
},
39+
state_schema: DollarRef { dref: None },
40+
supported_features: vec!["style_sunrise".to_string(), "intensity".to_string()],
41+
trigger_schema: DollarRef {
42+
dref: Some("trigger.json#".to_string()),
43+
},
44+
version: "0.0.1".to_string(),
45+
}
46+
}
47+
}
48+
49+
#[derive(Debug, Serialize, Deserialize, Clone)]
50+
pub struct BehaviorScriptMetadata {
51+
pub name: String,
52+
pub category: String,
53+
}
54+
55+
fn deserialize_optional_field<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
56+
where
57+
D: Deserializer<'de>,
58+
{
59+
Ok(Some(Value::deserialize(deserializer)?))
60+
}
61+
62+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
63+
pub struct BehaviorInstance {
64+
#[serde(default)]
65+
pub dependees: Vec<BehaviorInstanceDependee>,
66+
pub enabled: bool,
67+
pub last_error: Option<String>,
68+
pub metadata: BehaviorInstanceMetadata,
69+
pub script_id: Uuid,
70+
pub status: Option<String>,
71+
#[serde(
72+
default,
73+
deserialize_with = "deserialize_optional_field",
74+
skip_serializing_if = "Option::is_none"
75+
)]
76+
pub state: Option<Value>,
77+
#[serde(skip_serializing_if = "Option::is_none")]
78+
pub migrated_from: Option<Value>,
79+
pub configuration: Value,
80+
}
81+
82+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
83+
pub enum BehaviorInstanceConfiguration {
84+
Wakeup(WakeupConfiguration),
85+
}
86+
87+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88+
pub struct WakeupConfiguration {
89+
pub end_brightness: f64,
90+
pub fade_in_duration: configuration::Duration,
91+
#[serde(skip_serializing_if = "Option::is_none")]
92+
pub turn_lights_off_after: Option<configuration::Duration>,
93+
#[serde(skip_serializing_if = "Option::is_none")]
94+
pub style: Option<WakeupStyle>,
95+
pub when: configuration::When,
96+
#[serde(rename = "where")]
97+
pub where_field: Vec<configuration::Where>,
98+
}
99+
100+
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
101+
#[serde(rename_all = "lowercase")]
102+
pub enum WakeupStyle {
103+
Sunrise,
104+
Basic,
105+
}
106+
107+
pub mod configuration {
108+
use std::time::Duration as StdDuration;
109+
110+
use chrono::Weekday;
111+
use serde::{Deserialize, Serialize};
112+
113+
use crate::api::ResourceLink;
114+
115+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116+
pub struct Duration {
117+
pub seconds: u32,
118+
}
119+
120+
impl Duration {
121+
pub fn to_std(&self) -> StdDuration {
122+
StdDuration::from_secs(self.seconds.into())
123+
}
124+
}
125+
126+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127+
pub struct When {
128+
pub recurrence_days: Option<Vec<Weekday>>,
129+
pub time_point: TimePoint,
130+
}
131+
132+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133+
#[serde(tag = "type", rename_all = "snake_case")]
134+
pub enum TimePoint {
135+
Time { time: Time },
136+
}
137+
138+
impl TimePoint {
139+
pub const fn time(&self) -> &Time {
140+
match self {
141+
Self::Time { time } => time,
142+
}
143+
}
144+
}
145+
146+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147+
pub struct Time {
148+
pub hour: u32,
149+
pub minute: u32,
150+
}
151+
152+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153+
pub struct Where {
154+
pub group: ResourceLink,
155+
pub items: Option<Vec<ResourceLink>>,
156+
}
157+
}
158+
159+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
160+
pub struct BehaviorInstanceDependee {
161+
#[serde(rename = "type")]
162+
pub type_field: Option<String>,
163+
pub target: ResourceLink,
164+
pub level: BehaviorInstanceDependeeLevel,
165+
}
166+
167+
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
168+
#[serde(rename_all = "snake_case")]
169+
pub enum BehaviorInstanceDependeeLevel {
170+
Critical,
171+
NonCritical,
172+
}
173+
174+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
175+
pub struct BehaviorInstanceMetadata {
176+
pub name: String,
177+
}
178+
179+
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
180+
pub struct BehaviorInstanceUpdate {
181+
pub configuration: Option<Value>,
182+
pub enabled: Option<bool>,
183+
pub metadata: Option<BehaviorInstanceMetadata>,
184+
}
185+
186+
impl BehaviorInstanceUpdate {
187+
#[must_use]
188+
pub fn new() -> Self {
189+
Self::default()
190+
}
191+
192+
#[must_use]
193+
pub fn with_metadata(self, metadata: BehaviorInstanceMetadata) -> Self {
194+
Self {
195+
metadata: Some(metadata),
196+
..self
197+
}
198+
}
199+
200+
#[must_use]
201+
pub fn with_enabled(self, enabled: bool) -> Self {
202+
Self {
203+
enabled: Some(enabled),
204+
..self
205+
}
206+
}
207+
208+
#[must_use]
209+
pub fn with_configuration(self, configuration: Value) -> Self {
210+
Self {
211+
configuration: Some(configuration),
212+
..self
213+
}
214+
}
215+
}
216+
217+
impl AddAssign<BehaviorInstanceUpdate> for BehaviorInstance {
218+
fn add_assign(&mut self, upd: BehaviorInstanceUpdate) {
219+
if let Some(md) = upd.metadata {
220+
self.metadata = md;
221+
}
222+
223+
if let Some(enabled) = upd.enabled {
224+
self.enabled = enabled;
225+
}
226+
227+
if let Some(configuration) = upd.configuration {
228+
self.configuration = configuration;
229+
}
230+
}
231+
}

crates/hue/src/api/light.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ pub enum LightEffect {
490490
Cosmos,
491491
Sunbeam,
492492
Enchant,
493+
Sunrise,
493494
}
494495

495496
impl LightEffect {

crates/hue/src/api/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod behavior;
12
mod device;
23
mod entertainment;
34
mod entertainment_config;
@@ -11,6 +12,11 @@ mod stubs;
1112
mod update;
1213
mod zigbee_device_discovery;
1314

15+
pub use behavior::{
16+
BehaviorInstance, BehaviorInstanceConfiguration, BehaviorInstanceMetadata,
17+
BehaviorInstanceUpdate, BehaviorScript, BehaviorScriptMetadata, WakeupConfiguration,
18+
WakeupStyle,
19+
};
1420
pub use device::{Device, DeviceArchetype, DeviceProductData, DeviceUpdate, Identify};
1521
pub use entertainment::{Entertainment, EntertainmentSegment, EntertainmentSegments};
1622
pub use entertainment_config::{
@@ -44,11 +50,11 @@ pub use scene::{
4450
use serde::ser::SerializeMap;
4551
pub use stream::HueStreamKey;
4652
pub use stubs::{
47-
BehaviorInstance, BehaviorInstanceMetadata, BehaviorScript, Bridge, BridgeHome, Button,
48-
ButtonData, ButtonMetadata, ButtonReport, DevicePower, DeviceSoftwareUpdate, DollarRef,
49-
GeofenceClient, Geolocation, GroupedLightLevel, GroupedMotion, Homekit, LightLevel, Matter,
50-
Metadata, MetadataUpdate, Motion, PrivateGroup, PublicImage, RelativeRotary, SmartScene,
51-
Taurus, Temperature, TimeZone, ZigbeeConnectivity, ZigbeeConnectivityStatus, Zone,
53+
Bridge, BridgeHome, Button, ButtonData, ButtonMetadata, ButtonReport, DevicePower,
54+
DeviceSoftwareUpdate, DollarRef, GeofenceClient, Geolocation, GroupedLightLevel, GroupedMotion,
55+
Homekit, LightLevel, Matter, Metadata, MetadataUpdate, Motion, PrivateGroup, PublicImage,
56+
RelativeRotary, SmartScene, Taurus, Temperature, TimeZone, ZigbeeConnectivity,
57+
ZigbeeConnectivityStatus, Zone,
5258
};
5359
pub use update::Update;
5460
pub use zigbee_device_discovery::{

crates/hue/src/api/stubs.rs

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use std::collections::BTreeSet;
22

33
use chrono::{DateTime, Utc};
4-
use serde::{Deserialize, Deserializer, Serialize};
4+
use serde::{Deserialize, Serialize};
55
use serde_json::Value;
6-
use uuid::Uuid;
76

87
use crate::api::{DeviceArchetype, LightFunction, ResourceLink, SceneMetadata};
98
use crate::{best_guess_timezone, date_format};
@@ -71,51 +70,6 @@ pub struct DeviceSoftwareUpdate {
7170
pub problems: Vec<Value>,
7271
}
7372

74-
#[derive(Debug, Serialize, Deserialize, Clone)]
75-
pub struct BehaviorScript {
76-
pub configuration_schema: DollarRef,
77-
pub description: String,
78-
#[serde(skip_serializing_if = "Option::is_none")]
79-
pub max_number_instances: Option<u32>,
80-
pub metadata: Value,
81-
pub state_schema: DollarRef,
82-
pub supported_features: Vec<String>,
83-
pub trigger_schema: DollarRef,
84-
pub version: String,
85-
}
86-
87-
fn deserialize_optional_field<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
88-
where
89-
D: Deserializer<'de>,
90-
{
91-
Ok(Some(Value::deserialize(deserializer)?))
92-
}
93-
94-
#[derive(Debug, Serialize, Deserialize, Clone)]
95-
pub struct BehaviorInstance {
96-
pub configuration: Value,
97-
#[serde(default)]
98-
pub dependees: Vec<Value>,
99-
pub enabled: bool,
100-
pub last_error: Option<String>,
101-
pub metadata: BehaviorInstanceMetadata,
102-
pub script_id: Uuid,
103-
pub status: Option<String>,
104-
#[serde(
105-
default,
106-
deserialize_with = "deserialize_optional_field",
107-
skip_serializing_if = "Option::is_none"
108-
)]
109-
pub state: Option<Value>,
110-
#[serde(skip_serializing_if = "Option::is_none")]
111-
pub migrated_from: Option<Value>,
112-
}
113-
114-
#[derive(Debug, Serialize, Deserialize, Clone)]
115-
pub struct BehaviorInstanceMetadata {
116-
pub name: String,
117-
}
118-
11973
#[derive(Debug, Serialize, Deserialize, Clone)]
12074
pub struct GeofenceClient {
12175
pub name: String,

crates/hue/src/api/update.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ use serde::{Deserialize, Serialize};
22
use serde_json::Value;
33

44
use crate::api::{
5-
DeviceUpdate, EntertainmentConfigurationUpdate, GroupedLightUpdate, LightUpdate, RType,
6-
RoomUpdate, SceneUpdate,
5+
BehaviorInstanceUpdate, DeviceUpdate, EntertainmentConfigurationUpdate, GroupedLightUpdate,
6+
LightUpdate, RType, RoomUpdate, SceneUpdate,
77
};
88

99
type BridgeUpdate = Value;
1010
type BridgeHomeUpdate = Value;
1111
type ZigbeeDeviceDiscoveryUpdate = Value;
12-
type BehaviorInstanceUpdate = Value;
1312
type SmartSceneUpdate = Value;
1413
type ZoneUpdate = Value;
1514
type GeolocationUpdate = Value;

0 commit comments

Comments
 (0)