Skip to content
231 changes: 231 additions & 0 deletions crates/hue/src/api/behavior.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
use std::ops::AddAssign;

use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use uuid::{Uuid, uuid};

use super::{DollarRef, ResourceLink};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BehaviorScript {
pub configuration_schema: DollarRef,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_number_instances: Option<u32>,
pub metadata: BehaviorScriptMetadata,
pub state_schema: DollarRef,
pub supported_features: Vec<String>,
pub trigger_schema: DollarRef,
pub version: String,
}

impl BehaviorScript {
pub const WAKE_UP_ID: Uuid = uuid!("ff8957e3-2eb9-4699-a0c8-ad2cb3ede704");

#[must_use]
pub fn wake_up() -> Self {
Self {
configuration_schema: DollarRef {
dref: Some("basic_wake_up_config.json#".to_string()),
},
description:
"Get your body in the mood to wake up by fading on the lights in the morning."
.to_string(),
max_number_instances: None,
metadata: BehaviorScriptMetadata {
name: "Basic wake up routine".to_string(),
category: "automation".to_string(),
},
state_schema: DollarRef { dref: None },
supported_features: vec!["style_sunrise".to_string(), "intensity".to_string()],
trigger_schema: DollarRef {
dref: Some("trigger.json#".to_string()),
},
version: "0.0.1".to_string(),
}
}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BehaviorScriptMetadata {
pub name: String,
pub category: String,
}

fn deserialize_optional_field<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(Value::deserialize(deserializer)?))
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct BehaviorInstance {
#[serde(default)]
pub dependees: Vec<BehaviorInstanceDependee>,
pub enabled: bool,
pub last_error: Option<String>,
pub metadata: BehaviorInstanceMetadata,
pub script_id: Uuid,
pub status: Option<String>,
#[serde(
default,
deserialize_with = "deserialize_optional_field",
skip_serializing_if = "Option::is_none"
)]
pub state: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub migrated_from: Option<Value>,
pub configuration: Value,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum BehaviorInstanceConfiguration {
Wakeup(WakeupConfiguration),
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WakeupConfiguration {
pub end_brightness: f64,
pub fade_in_duration: configuration::Duration,
#[serde(skip_serializing_if = "Option::is_none")]
pub turn_lights_off_after: Option<configuration::Duration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<WakeupStyle>,
pub when: configuration::When,
#[serde(rename = "where")]
pub where_field: Vec<configuration::Where>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum WakeupStyle {
Sunrise,
Basic,
}

pub mod configuration {
use std::time::Duration as StdDuration;

use chrono::Weekday;
use serde::{Deserialize, Serialize};

use crate::api::ResourceLink;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Duration {
pub seconds: u32,
}

impl Duration {
pub fn to_std(&self) -> StdDuration {
StdDuration::from_secs(self.seconds.into())
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct When {
pub recurrence_days: Option<Vec<Weekday>>,
pub time_point: TimePoint,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TimePoint {
Time { time: Time },
}

impl TimePoint {
pub const fn time(&self) -> &Time {
match self {
Self::Time { time } => time,
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Time {
pub hour: u32,
pub minute: u32,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Where {
pub group: ResourceLink,
pub items: Option<Vec<ResourceLink>>,
}
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct BehaviorInstanceDependee {
#[serde(rename = "type")]
pub type_field: Option<String>,
pub target: ResourceLink,
pub level: BehaviorInstanceDependeeLevel,
}

#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BehaviorInstanceDependeeLevel {
Critical,
NonCritical,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct BehaviorInstanceMetadata {
pub name: String,
}

#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct BehaviorInstanceUpdate {
pub configuration: Option<Value>,
pub enabled: Option<bool>,
pub metadata: Option<BehaviorInstanceMetadata>,
}

impl BehaviorInstanceUpdate {
#[must_use]
pub fn new() -> Self {
Self::default()
}

#[must_use]
pub fn with_metadata(self, metadata: BehaviorInstanceMetadata) -> Self {
Self {
metadata: Some(metadata),
..self
}
}

#[must_use]
pub fn with_enabled(self, enabled: bool) -> Self {
Self {
enabled: Some(enabled),
..self
}
}

#[must_use]
pub fn with_configuration(self, configuration: Value) -> Self {
Self {
configuration: Some(configuration),
..self
}
}
}

impl AddAssign<BehaviorInstanceUpdate> for BehaviorInstance {
fn add_assign(&mut self, upd: BehaviorInstanceUpdate) {
if let Some(md) = upd.metadata {
self.metadata = md;
}

if let Some(enabled) = upd.enabled {
self.enabled = enabled;
}

if let Some(configuration) = upd.configuration {
self.configuration = configuration;
}
}
}
1 change: 1 addition & 0 deletions crates/hue/src/api/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ pub enum LightEffect {
Cosmos,
Sunbeam,
Enchant,
Sunrise,
}

impl LightEffect {
Expand Down
16 changes: 11 additions & 5 deletions crates/hue/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod behavior;
mod device;
mod entertainment;
mod entertainment_config;
Expand All @@ -11,6 +12,11 @@ mod stubs;
mod update;
mod zigbee_device_discovery;

pub use behavior::{
BehaviorInstance, BehaviorInstanceConfiguration, BehaviorInstanceMetadata,
BehaviorInstanceUpdate, BehaviorScript, BehaviorScriptMetadata, WakeupConfiguration,
WakeupStyle,
};
pub use device::{Device, DeviceArchetype, DeviceProductData, DeviceUpdate, Identify};
pub use entertainment::{Entertainment, EntertainmentSegment, EntertainmentSegments};
pub use entertainment_config::{
Expand Down Expand Up @@ -44,11 +50,11 @@ pub use scene::{
use serde::ser::SerializeMap;
pub use stream::HueStreamKey;
pub use stubs::{
BehaviorInstance, BehaviorInstanceMetadata, BehaviorScript, Bridge, BridgeHome, Button,
ButtonData, ButtonMetadata, ButtonReport, DevicePower, DeviceSoftwareUpdate, DollarRef,
GeofenceClient, Geolocation, GroupedLightLevel, GroupedMotion, Homekit, LightLevel, Matter,
Metadata, MetadataUpdate, Motion, PrivateGroup, PublicImage, RelativeRotary, SmartScene,
Taurus, Temperature, TimeZone, ZigbeeConnectivity, ZigbeeConnectivityStatus, Zone,
Bridge, BridgeHome, Button, ButtonData, ButtonMetadata, ButtonReport, DevicePower,
DeviceSoftwareUpdate, DollarRef, GeofenceClient, Geolocation, GroupedLightLevel, GroupedMotion,
Homekit, LightLevel, Matter, Metadata, MetadataUpdate, Motion, PrivateGroup, PublicImage,
RelativeRotary, SmartScene, Taurus, Temperature, TimeZone, ZigbeeConnectivity,
ZigbeeConnectivityStatus, Zone,
};
pub use update::Update;
pub use zigbee_device_discovery::{
Expand Down
48 changes: 1 addition & 47 deletions crates/hue/src/api/stubs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::collections::BTreeSet;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;

use crate::api::{DeviceArchetype, LightFunction, ResourceLink, SceneMetadata};
use crate::{best_guess_timezone, date_format};
Expand Down Expand Up @@ -71,51 +70,6 @@ pub struct DeviceSoftwareUpdate {
pub problems: Vec<Value>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BehaviorScript {
pub configuration_schema: DollarRef,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_number_instances: Option<u32>,
pub metadata: Value,
pub state_schema: DollarRef,
pub supported_features: Vec<String>,
pub trigger_schema: DollarRef,
pub version: String,
}

fn deserialize_optional_field<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(Value::deserialize(deserializer)?))
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BehaviorInstance {
pub configuration: Value,
#[serde(default)]
pub dependees: Vec<Value>,
pub enabled: bool,
pub last_error: Option<String>,
pub metadata: BehaviorInstanceMetadata,
pub script_id: Uuid,
pub status: Option<String>,
#[serde(
default,
deserialize_with = "deserialize_optional_field",
skip_serializing_if = "Option::is_none"
)]
pub state: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub migrated_from: Option<Value>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BehaviorInstanceMetadata {
pub name: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GeofenceClient {
pub name: String,
Expand Down
5 changes: 2 additions & 3 deletions crates/hue/src/api/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::api::{
DeviceUpdate, EntertainmentConfigurationUpdate, GroupedLightUpdate, LightUpdate, RType,
RoomUpdate, SceneUpdate,
BehaviorInstanceUpdate, DeviceUpdate, EntertainmentConfigurationUpdate, GroupedLightUpdate,
LightUpdate, RType, RoomUpdate, SceneUpdate,
};

type BridgeUpdate = Value;
type BridgeHomeUpdate = Value;
type ZigbeeDeviceDiscoveryUpdate = Value;
type BehaviorInstanceUpdate = Value;
type SmartSceneUpdate = Value;
type ZoneUpdate = Value;
type GeolocationUpdate = Value;
Expand Down
Loading