Skip to content

Commit 8e16fba

Browse files
authored
Merge pull request #141 from duvholt/light-transition
Implement support for transition (color, brightness)
2 parents 4740f8a + 875317f commit 8e16fba

File tree

7 files changed

+86
-8
lines changed

7 files changed

+86
-8
lines changed

crates/hue/src/api/grouped_light.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,27 @@ impl GroupedLight {
4747
}
4848
}
4949

50+
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
51+
pub struct GroupedLightDynamicsUpdate {
52+
#[serde(skip_serializing_if = "Option::is_none")]
53+
pub duration: Option<u32>,
54+
}
55+
56+
impl GroupedLightDynamicsUpdate {
57+
#[must_use]
58+
pub fn new() -> Self {
59+
Self::default()
60+
}
61+
62+
#[must_use]
63+
pub fn with_duration(self, duration: Option<impl Into<u32>>) -> Self {
64+
Self {
65+
duration: duration.map(Into::into),
66+
..self
67+
}
68+
}
69+
}
70+
5071
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
5172
pub struct GroupedLightUpdate {
5273
#[serde(skip_serializing_if = "Option::is_none")]
@@ -59,6 +80,8 @@ pub struct GroupedLightUpdate {
5980
pub color_temperature: Option<ColorTemperatureUpdate>,
6081
#[serde(skip_serializing_if = "Option::is_none")]
6182
pub owner: Option<ResourceLink>,
83+
#[serde(skip_serializing_if = "Option::is_none")]
84+
pub dynamics: Option<GroupedLightDynamicsUpdate>,
6285
}
6386

6487
impl GroupedLightUpdate {
@@ -103,6 +126,11 @@ impl GroupedLightUpdate {
103126
..self
104127
}
105128
}
129+
130+
#[must_use]
131+
pub const fn with_dynamics(self, dynamics: Option<GroupedLightDynamicsUpdate>) -> Self {
132+
Self { dynamics, ..self }
133+
}
106134
}
107135

108136
/* conversion from v1 api */
@@ -113,5 +141,9 @@ impl From<&ApiLightStateUpdate> for GroupedLightUpdate {
113141
.with_brightness(upd.bri.map(|b| f64::from(b) / 2.54))
114142
.with_color_xy(upd.xy.map(XY::from))
115143
.with_color_temperature(upd.ct)
144+
.with_dynamics(
145+
upd.transitiontime
146+
.map(|t| GroupedLightDynamicsUpdate::new().with_duration(Some(t * 100))),
147+
)
116148
}
117149
}

crates/hue/src/api/light.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -449,16 +449,27 @@ pub struct LightDynamics {
449449
pub speed_valid: bool,
450450
}
451451

452-
#[derive(Debug, Serialize, Deserialize, Clone)]
452+
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
453453
pub struct LightDynamicsUpdate {
454-
#[serde(skip_serializing_if = "Option::is_none")]
455-
pub status: Option<LightDynamicsStatus>,
456-
#[serde(skip_serializing_if = "Option::is_none")]
457-
pub status_values: Option<Vec<LightDynamicsStatus>>,
458454
#[serde(skip_serializing_if = "Option::is_none")]
459455
pub speed: Option<f64>,
460456
#[serde(skip_serializing_if = "Option::is_none")]
461-
pub speed_valid: Option<bool>,
457+
pub duration: Option<u32>,
458+
}
459+
460+
impl LightDynamicsUpdate {
461+
#[must_use]
462+
pub fn new() -> Self {
463+
Self::default()
464+
}
465+
466+
#[must_use]
467+
pub fn with_duration(self, duration: Option<impl Into<u32>>) -> Self {
468+
Self {
469+
duration: duration.map(Into::into),
470+
..self
471+
}
472+
}
462473
}
463474

464475
impl Default for LightDynamics {
@@ -688,6 +699,11 @@ impl LightUpdate {
688699
pub fn with_gradient(self, gradient: Option<LightGradientUpdate>) -> Self {
689700
Self { gradient, ..self }
690701
}
702+
703+
#[must_use]
704+
pub fn with_dynamics(self, dynamics: Option<LightDynamicsUpdate>) -> Self {
705+
Self { dynamics, ..self }
706+
}
691707
}
692708

693709
impl From<&ApiLightStateUpdate> for LightUpdate {
@@ -698,6 +714,10 @@ impl From<&ApiLightStateUpdate> for LightUpdate {
698714
.with_color_temperature(upd.ct)
699715
.with_color_hs(upd.hs.map(Into::into))
700716
.with_color_xy(upd.xy.map(Into::into))
717+
.with_dynamics(
718+
upd.transitiontime
719+
.map(|t| LightDynamicsUpdate::new().with_duration(Some(t * 100))),
720+
)
701721
}
702722
}
703723

crates/hue/src/api/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ impl<'a> V1Reply<'a> {
382382
self.add_option("on", upd.on)?
383383
.add_option("bri", upd.bri)?
384384
.add_option("xy", upd.xy)?
385-
.add_option("ct", upd.ct)
385+
.add_option("ct", upd.ct)?
386+
.add_option("transitiontime", upd.transitiontime)
386387
}
387388

388389
pub fn add<T: Serialize>(mut self, name: &'a str, value: T) -> HueResult<Self> {

crates/hue/src/legacy_api.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,8 @@ pub struct ApiLightStateUpdate {
484484
pub ct: Option<u16>,
485485
#[serde(skip_serializing_if = "Option::is_none", flatten)]
486486
pub hs: Option<RawHS>,
487+
#[serde(skip_serializing_if = "Option::is_none")]
488+
pub transitiontime: Option<u16>,
487489
}
488490

489491
#[derive(Debug, Serialize, Deserialize)]
@@ -519,6 +521,7 @@ impl From<api::SceneAction> for ApiLightStateUpdate {
519521
xy: action.color.map(|col| col.xy.into()),
520522
ct: action.color_temperature.and_then(|ct| ct.mirek),
521523
hs: None,
524+
transitiontime: None,
522525
}
523526
}
524527
}

crates/z2m/src/convert.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,10 @@ impl From<&GroupedLightUpdate> for DeviceUpdate {
190190
.with_brightness(upd.dimming.map(|dim| dim.brightness / 100.0 * 254.0))
191191
.with_color_temp(upd.color_temperature.and_then(|ct| ct.mirek))
192192
.with_color_xy(upd.color.map(|col| col.xy))
193+
.with_transition(
194+
upd.dynamics
195+
.as_ref()
196+
.and_then(|d| d.duration.map(|duration| f64::from(duration) / 1000.0)),
197+
)
193198
}
194199
}

crates/z2m/src/update.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ impl DeviceUpdate {
122122
..self
123123
}
124124
}
125+
126+
#[must_use]
127+
pub fn with_transition(self, transition: Option<f64>) -> Self {
128+
Self { transition, ..self }
129+
}
125130
}
126131

127132
#[derive(Copy, Debug, Serialize, Deserialize, Clone)]

src/backend/z2m/backend_event.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,23 @@ impl Z2mBackend {
9292
drop(lock);
9393

9494
/* step 1: send generic light update */
95+
let transition = upd
96+
.dynamics
97+
.as_ref()
98+
.and_then(|d| d.duration.map(|duration| f64::from(duration) / 1000.0))
99+
.or_else(|| {
100+
if upd.dimming.is_some() || upd.color_temperature.is_some() || upd.color.is_some() {
101+
Some(0.4)
102+
} else {
103+
None
104+
}
105+
});
95106
let mut payload = DeviceUpdate::default()
96107
.with_state(upd.on.map(|on| on.on))
97108
.with_brightness(upd.dimming.map(|dim| dim.brightness / 100.0 * 254.0))
98109
.with_color_temp(upd.color_temperature.and_then(|ct| ct.mirek))
99-
.with_color_xy(upd.color.map(|col| col.xy));
110+
.with_color_xy(upd.color.map(|col| col.xy))
111+
.with_transition(transition);
100112

101113
// We don't want to send gradient updates twice, but if hue
102114
// effects are not supported for this light, this is the best

0 commit comments

Comments
 (0)