Skip to main content

hues/service/
sensor.rs

1use crate::{
2    api::HueAPIError,
3    command::{
4        merge_commands, BasicCommand, GeofenceClientCommand, GeolocationCommand, MotionCommand,
5    },
6    service::{Bridge, ResourceIdentifier, ResourceType, SetStatus},
7};
8use serde::{Deserialize, Serialize};
9
10/// A physical contact sensor device.
11#[derive(Debug)]
12pub struct Contact<'a> {
13    bridge: &'a Bridge,
14    data: ContactData,
15}
16
17impl<'a> Contact<'a> {
18    pub fn new(bridge: &'a Bridge, data: ContactData) -> Self {
19        Contact { bridge, data }
20    }
21
22    pub fn data(&self) -> &ContactData {
23        &self.data
24    }
25
26    pub fn id(&self) -> &str {
27        &self.data.id
28    }
29
30    pub fn rid(&self) -> ResourceIdentifier {
31        self.data.rid()
32    }
33
34    pub async fn send(
35        &self,
36        commands: &[BasicCommand],
37    ) -> Result<Vec<ResourceIdentifier>, HueAPIError> {
38        let payload = merge_commands(commands);
39        self.bridge.api.put_contact(self.id(), &payload).await
40    }
41}
42
43/// Internal representation of a [Contact].
44#[derive(Clone, Debug, Deserialize)]
45pub struct ContactData {
46    /// Unique identifier representing a specific resource instance.
47    pub id: String,
48    /// Clip v1 resource identifier.
49    pub id_v1: Option<String>,
50    /// Owner of the service, in case the owner service is deleted, the service also gets deleted.
51    pub owner: ResourceIdentifier,
52    /// Whether sensor is activated or not.
53    pub enabled: bool,
54    pub contact_report: Option<ContactReport>,
55}
56
57impl ContactData {
58    pub fn rid(&self) -> ResourceIdentifier {
59        ResourceIdentifier {
60            rid: self.id.to_owned(),
61            rtype: ResourceType::Contact,
62        }
63    }
64}
65
66#[derive(Clone, Debug, Deserialize)]
67pub struct ContactReport {
68    /// Last time the value of this property was updated.
69    pub changed: String,
70    pub state: ContactStatus,
71}
72
73#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq)]
74#[serde(rename_all = "snake_case")]
75pub enum ContactStatus {
76    Contact,
77    NoContact,
78}
79
80/// A motion detection senseor device.
81#[derive(Debug)]
82pub struct Motion<'a> {
83    bridge: &'a Bridge,
84    data: MotionData,
85}
86
87impl<'a> Motion<'a> {
88    pub fn new(bridge: &'a Bridge, data: MotionData) -> Self {
89        Motion { bridge, data }
90    }
91
92    pub fn data(&self) -> &MotionData {
93        &self.data
94    }
95
96    pub fn id(&self) -> &str {
97        &self.data.id
98    }
99
100    pub fn rid(&self) -> ResourceIdentifier {
101        ResourceIdentifier {
102            rid: self.id().to_owned(),
103            rtype: ResourceType::Motion,
104        }
105    }
106
107    pub async fn send(
108        &self,
109        commands: &[MotionCommand],
110    ) -> Result<Vec<ResourceIdentifier>, HueAPIError> {
111        let payload = merge_commands(commands);
112        self.bridge.api.put_motion(self.id(), &payload).await
113    }
114}
115
116#[derive(Debug)]
117pub struct CameraMotion<'a> {
118    bridge: &'a Bridge,
119    data: MotionData,
120}
121
122/// A camera device with motion detection capability.
123impl<'a> CameraMotion<'a> {
124    pub fn new(bridge: &'a Bridge, data: MotionData) -> Self {
125        CameraMotion { bridge, data }
126    }
127
128    pub fn data(&self) -> &MotionData {
129        &self.data
130    }
131
132    pub fn id(&self) -> &str {
133        &self.data.id
134    }
135
136    pub fn rid(&self) -> ResourceIdentifier {
137        ResourceIdentifier {
138            rid: self.id().to_owned(),
139            rtype: ResourceType::CameraMotion,
140        }
141    }
142
143    pub async fn send(
144        &self,
145        commands: &[MotionCommand],
146    ) -> Result<Vec<ResourceIdentifier>, HueAPIError> {
147        let payload = merge_commands(commands);
148        self.bridge.api.put_camera_motion(self.id(), &payload).await
149    }
150}
151
152/// Internal representation of a [Motion] or [CameraMotion].
153#[derive(Clone, Debug, Deserialize)]
154pub struct MotionData {
155    /// Unique identifier representing a specific resource instance.
156    pub id: String,
157    /// Clip v1 resource identifier.
158    pub id_v1: Option<String>,
159    /// Owner of the service, in case the owner service is deleted, the service also gets deleted.
160    pub owner: ResourceIdentifier,
161    /// Whether sensor is activated or not.
162    pub enabled: bool,
163    pub motion: MotionState,
164    pub sensitivity: Option<Sensitivity>,
165}
166
167#[derive(Clone, Debug, Deserialize)]
168pub struct MotionState {
169    /// Motion is valid when `motion_report` property is present, invalid when absent.
170    #[deprecated]
171    pub motion_valid: bool,
172    pub motion_report: Option<MotionReport>,
173}
174
175#[derive(Clone, Debug, Deserialize)]
176pub struct MotionReport {
177    /// Last time the value of this property is changed.
178    pub changed: String,
179    /// `true` if motion is detected/
180    pub motion: bool,
181}
182
183#[derive(Clone, Debug, Deserialize)]
184pub struct Sensitivity {
185    pub status: SetStatus,
186    /// Sensitivity of the sensor. Value in the range `0` to `sensitivity_max`.
187    pub sensitivity: usize,
188    /// Maximum value of the sensitivity configuration attribute.
189    pub sensitivity_max: Option<usize>,
190}
191
192/// A temperature sensor device.
193#[derive(Debug)]
194pub struct Temperature<'a> {
195    bridge: &'a Bridge,
196    data: TemperatureData,
197}
198
199impl<'a> Temperature<'a> {
200    pub fn new(bridge: &'a Bridge, data: TemperatureData) -> Self {
201        Temperature { bridge, data }
202    }
203
204    pub fn data(&self) -> &TemperatureData {
205        &self.data
206    }
207
208    pub fn id(&self) -> &str {
209        &self.data.id
210    }
211
212    pub fn rid(&self) -> ResourceIdentifier {
213        self.data.rid()
214    }
215
216    pub async fn send(
217        &self,
218        commands: &[BasicCommand],
219    ) -> Result<Vec<ResourceIdentifier>, HueAPIError> {
220        let payload = merge_commands(commands);
221        self.bridge.api.put_temperature(self.id(), &payload).await
222    }
223}
224
225/// Internal representation of a [Temperature].
226#[derive(Clone, Debug, Deserialize)]
227pub struct TemperatureData {
228    /// Unique identifier representing a specific resource instance.
229    pub id: String,
230    /// Clip v1 resource identifier.
231    pub id_v1: Option<String>,
232    /// Owner of the service, in case the owner service is deleted, the service also gets deleted.
233    pub owner: ResourceIdentifier,
234    /// Whether sensor is activated or not.
235    pub enabled: bool,
236    pub temperature: TemperatureState,
237}
238
239impl TemperatureData {
240    pub fn rid(&self) -> ResourceIdentifier {
241        ResourceIdentifier {
242            rid: self.id.to_owned(),
243            rtype: ResourceType::Temperature,
244        }
245    }
246}
247
248#[derive(Clone, Debug, Deserialize)]
249pub struct TemperatureState {
250    #[deprecated]
251    pub temperature: f32,
252    #[deprecated]
253    pub temperature_valid: bool,
254    pub temperature_report: Option<TemperatureReport>,
255}
256
257#[derive(Clone, Debug, Deserialize)]
258pub struct TemperatureReport {
259    /// Last time the value of this property is changed.
260    pub changed: String,
261    pub temperature: f32,
262}
263
264/// A light level detection device.
265#[derive(Debug)]
266pub struct LightLevel<'a> {
267    bridge: &'a Bridge,
268    data: LightLevelData,
269}
270
271impl<'a> LightLevel<'a> {
272    pub fn new(bridge: &'a Bridge, data: LightLevelData) -> Self {
273        LightLevel { bridge, data }
274    }
275
276    pub fn data(&self) -> &LightLevelData {
277        &self.data
278    }
279
280    pub fn id(&self) -> &str {
281        &self.data.id
282    }
283
284    pub fn rid(&self) -> ResourceIdentifier {
285        self.data.rid()
286    }
287
288    pub async fn send(
289        &self,
290        commands: &[BasicCommand],
291    ) -> Result<Vec<ResourceIdentifier>, HueAPIError> {
292        let payload = merge_commands(commands);
293        self.bridge.api.put_light_level(self.id(), &payload).await
294    }
295}
296
297/// Internal representation of a [LightLevel].
298#[derive(Clone, Debug, Deserialize)]
299pub struct LightLevelData {
300    /// Unique identifier representing a specific resource instance.
301    pub id: String,
302    /// Clip v1 resource identifier.
303    pub id_v1: Option<String>,
304    /// Owner of the service, in case the owner service is deleted, the service also gets deleted.
305    pub owner: ResourceIdentifier,
306    /// Whether sensor is activated or not.
307    pub enabled: bool,
308    pub light: LightLevelState,
309}
310
311impl LightLevelData {
312    pub fn rid(&self) -> ResourceIdentifier {
313        ResourceIdentifier {
314            rid: self.id.to_owned(),
315            rtype: ResourceType::LightLevel,
316        }
317    }
318}
319
320#[derive(Clone, Debug, Deserialize)]
321pub struct LightLevelState {
322    #[deprecated]
323    pub light_level: usize,
324    #[deprecated]
325    pub light_level_valid: bool,
326    pub light_level_report: Option<LightLevelReport>,
327}
328
329#[derive(Clone, Debug, Deserialize)]
330pub struct LightLevelReport {
331    /// Last time the value of this property is changed.
332    pub changed: String,
333    /// Light level in `10000*log10(lux) + 1` measured by sensor.
334    /// Logarithmic scale used because the human eye adjusts to light levels and small changes at
335    /// low lux levels are more noticeable than at high lux levels.
336    /// This allows use of linear scale configuration sliders.
337    pub light_level: usize,
338}
339
340/// A virtual device representing the location of the Hue Bridge.
341#[derive(Debug)]
342pub struct Geolocation<'a> {
343    bridge: &'a Bridge,
344    data: GeolocationData,
345}
346
347impl<'a> Geolocation<'a> {
348    pub fn new(bridge: &'a Bridge, data: GeolocationData) -> Self {
349        Geolocation { bridge, data }
350    }
351
352    pub fn data(&self) -> &GeolocationData {
353        &self.data
354    }
355
356    pub fn id(&self) -> &str {
357        &self.data.id
358    }
359
360    pub fn rid(&self) -> ResourceIdentifier {
361        self.data.rid()
362    }
363
364    pub async fn send(
365        &self,
366        commands: &[GeolocationCommand],
367    ) -> Result<Vec<ResourceIdentifier>, HueAPIError> {
368        let payload = merge_commands(commands);
369        self.bridge.api.put_geolocation(self.id(), &payload).await
370    }
371}
372
373/// Internal representation of the device [Geolocation].
374#[derive(Clone, Debug, Deserialize)]
375pub struct GeolocationData {
376    /// Unique identifier representing a specific resource instance.
377    pub id: String,
378    /// Clip v1 resource identifier.
379    pub id_v1: Option<String>,
380    /// Is the geolocation configured.
381    pub is_configured: bool,
382    /// Info related to today's sun (only available when geolocation has been configured).
383    pub sun_today: Option<SunToday>,
384}
385
386impl GeolocationData {
387    pub fn rid(&self) -> ResourceIdentifier {
388        ResourceIdentifier {
389            rid: self.id.to_owned(),
390            rtype: ResourceType::Geolocation,
391        }
392    }
393}
394
395#[derive(Clone, Debug, Deserialize)]
396pub struct SunToday {
397    pub sunset_time: String,
398    pub day_type: DayType,
399}
400
401#[derive(Clone, Debug, Deserialize)]
402#[serde(rename_all = "snake_case")]
403pub enum DayType {
404    NormalDay,
405    PolarDay,
406    PolarNight,
407    #[serde(other)]
408    Unknown,
409}
410
411/// A virtual device representing a location-based trigger.
412#[derive(Debug)]
413pub struct GeofenceClient<'a> {
414    bridge: &'a Bridge,
415    data: GeofenceClientData,
416}
417
418impl<'a> GeofenceClient<'a> {
419    pub fn new(bridge: &'a Bridge, data: GeofenceClientData) -> Self {
420        GeofenceClient { bridge, data }
421    }
422
423    pub fn data(&self) -> &GeofenceClientData {
424        &self.data
425    }
426
427    pub fn id(&self) -> &str {
428        &self.data.id
429    }
430
431    pub fn rid(&self) -> ResourceIdentifier {
432        self.data.rid()
433    }
434
435    pub fn builder(name: impl Into<String>) -> GeofenceClientBuilder {
436        GeofenceClientBuilder::new(name)
437    }
438
439    pub async fn send(
440        &self,
441        commands: &[GeofenceClientCommand],
442    ) -> Result<Vec<ResourceIdentifier>, HueAPIError> {
443        let payload = merge_commands(commands);
444        self.bridge
445            .api
446            .put_geofence_client(self.id(), &payload)
447            .await
448    }
449}
450
451/// Internal representation of a [GeofenceClient].
452#[derive(Clone, Debug, Deserialize)]
453pub struct GeofenceClientData {
454    /// Unique identifier representing a specific resource instance.
455    pub id: String,
456    /// Clip v1 resource identifier.
457    pub id_v1: Option<String>,
458    pub name: String,
459}
460
461impl GeofenceClientData {
462    pub fn rid(&self) -> ResourceIdentifier {
463        ResourceIdentifier {
464            rid: self.id.to_owned(),
465            rtype: ResourceType::GeofenceClient,
466        }
467    }
468}
469
470#[derive(Serialize)]
471pub struct GeofenceClientBuilder {
472    is_at_home: bool,
473    name: String,
474}
475
476impl GeofenceClientBuilder {
477    pub fn new(name: impl Into<String>) -> Self {
478        GeofenceClientBuilder {
479            is_at_home: true,
480            name: name.into(),
481        }
482    }
483
484    pub fn is_at_home(mut self, b: bool) -> Self {
485        self.is_at_home = b;
486        self
487    }
488}
489
490/// A tamper detection device.
491#[derive(Debug)]
492pub struct Tamper {
493    data: TamperData,
494}
495
496impl Tamper {
497    pub fn new(data: TamperData) -> Self {
498        Tamper { data }
499    }
500
501    pub fn data(&self) -> &TamperData {
502        &self.data
503    }
504
505    pub fn id(&self) -> &str {
506        &self.data.id
507    }
508
509    pub fn rid(&self) -> ResourceIdentifier {
510        self.data.rid()
511    }
512}
513
514/// Internal representation of a [Tamper].
515#[derive(Clone, Debug, Deserialize)]
516pub struct TamperData {
517    /// Unique identifier representing a specific resource instance.
518    pub id: String,
519    /// Clip v1 resource identifier.
520    pub id_v1: Option<String>,
521    /// Owner of the service, in case the owner service is deleted, the service also gets deleted.
522    pub owner: ResourceIdentifier,
523    pub tamper_reports: Vec<TamperReport>,
524}
525
526impl TamperData {
527    pub fn rid(&self) -> ResourceIdentifier {
528        ResourceIdentifier {
529            rid: self.id.to_owned(),
530            rtype: ResourceType::Tamper,
531        }
532    }
533}
534
535#[derive(Clone, Debug, Deserialize)]
536pub struct TamperReport {
537    /// Last time the value of this property is changed.
538    pub changed: String,
539    /// Source of tamper and time expired since last change of tamper-state.
540    pub source: String,
541    /// The state of tamper after last change.
542    pub state: TamperStatus,
543}
544
545#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq)]
546#[serde(rename_all = "snake_case")]
547pub enum TamperStatus {
548    Tampered,
549    NotTampered,
550}