dss/
lib.rs

1/// The appartment gives you a easy and highlevel interface
2/// to a dss installation. It's the main struct this crates
3/// provides.
4///
5/// Please only use this interaface to a dss installation and
6/// not any other interface to the same installation at the same time.
7/// This crate is buffering information to keep the dss actions
8/// reponse times somewhat decent.
9/// Also don't use the RawAPI directly, because it has the same effect as
10/// using another interface and is leading to mismathcing data.
11/// This is unfortunatelly due to the really bad designed API from DSS.
12#[derive(Debug, Clone)]
13pub struct Appartement {
14    inner: std::sync::Arc<std::sync::Mutex<InnerAppartement>>,
15}
16
17impl Appartement {
18    /// Connect to a DSS installation and fetch the complete structure of it.
19    /// Based on your appartment size, this can take around a minute.
20    pub fn connect<S>(host: S, user: S, password: S) -> Result<Appartement>
21    where
22        S: Into<String>,
23    {
24        // create the Appartment with the inner values
25        let appt = Appartement {
26            inner: std::sync::Arc::new(std::sync::Mutex::new(InnerAppartement {
27                api: RawApi::connect(host, user, password)?,
28                zones: vec![],
29                thread: std::sync::Arc::new(std::sync::Mutex::new(false)),
30            })),
31        };
32
33        // update the complete structure
34        appt.inner.lock()?.update_structure()?;
35
36        Ok(appt)
37    }
38
39    /// Returns an vector of all zones with their groups.
40    ///
41    /// Keep in mind, that the values are in a frozen state.
42    /// If you want to stay informed about changes, use the
43    /// 'event_channel()' function.
44    pub fn get_zones(&self) -> Result<Vec<Zone>> {
45        Ok(self.inner.lock()?.zones.clone())
46    }
47
48    /// Updates the complete appartment structure, this command can take some time
49    /// to execute (more than 30 seconds).
50    ///
51    /// Use the 'get_zones()' function to get the actual structure with updates
52    /// values for each group.
53    pub fn update_all(&self) -> Result<Vec<Zone>> {
54        self.inner.lock()?.update_structure()?;
55        Ok(self.inner.lock()?.zones.clone())
56    }
57
58    pub fn set_value(&self, zone: usize, group: Option<usize>, value: Value) -> Result<()> {
59        self.inner.lock()?.set_value(zone, group, value)
60    }
61
62    /// Get the event channel for the appartment.
63    ///
64    /// When a channel is already open for this apparment, we close the open one
65    /// and create a new one which gets returned.
66    /// Therefore it's not recommended to call this function twice for one appartment.
67    pub fn event_channel(&self) -> Result<std::sync::mpsc::Receiver<Event>> {
68        // when there are threads already existing close them
69        if *self.inner.lock()?.thread.lock()? == true {
70            *self.inner.lock()?.thread.lock()? = false;
71        }
72
73        // request a new event channel
74        let (recv, status) = self.inner.lock()?.api.new_event_channel()?;
75
76        // create the new out channel
77        let (inp, out) = std::sync::mpsc::channel();
78
79        // clone the status for the thread & the structure
80        let internal_status = status.clone();
81        let appr = self.inner.clone();
82
83        std::thread::spawn(move || loop {
84            // listen for events to pop up
85            let event = match recv.recv() {
86                Ok(e) => e,
87                Err(_) => break,
88            };
89
90            // check if the thread should be ended
91            if *internal_status.lock().unwrap() == false {
92                break;
93            }
94
95            // expand the events when necessary
96            let events = appr.lock().unwrap().expand_value(event).unwrap();
97
98            for event in events {
99                // update the event value for shadow etc.
100                let event = appr.lock().unwrap().update_event_value(event).unwrap();
101
102                // update the appartment structure with the event
103                appr.lock().unwrap().zones.iter_mut().for_each(|z| {
104                    // fine the right zone to the event
105                    if z.id == event.zone {
106                        z.groups.iter_mut().for_each(|g| {
107                            // find the right group typ && id to update the value
108                            if g.typ == event.typ && g.id == event.group {
109                                g.status = event.value.clone();
110                            }
111                        });
112                    }
113                });
114
115                // send the event
116                match inp.send(event) {
117                    Ok(_) => {}
118                    Err(_) => break,
119                }
120            }
121        });
122
123        // update the thread status with the new one received
124        self.inner.lock()?.thread = status;
125        Ok(out)
126    }
127}
128
129#[derive(Debug)]
130struct InnerAppartement {
131    api: RawApi,
132    zones: Vec<Zone>,
133    thread: std::sync::Arc<std::sync::Mutex<bool>>,
134}
135
136impl InnerAppartement {
137    fn set_value(&mut self, zone: usize, group: Option<usize>, value: Value) -> Result<()> {
138        // when a group exist we control the special group
139        if let Some(grp) = group {
140            match value {
141                // depending on the value we turn the light on or off
142                Value::Light(light) => {
143                    if light < 0.5 {
144                        self.api.call_action(zone, Action::LightOff(grp))?;
145                    } else {
146                        self.api.call_action(zone, Action::LightOn(grp))?;
147                    }
148                }
149                // actions need to be performed for setting the shadow
150                Value::Shadow(open, angle) => {
151                    if open <= 0.1 {
152                        self.api.call_action(zone, Action::ShadowUp(grp))?;
153                    }
154                    if open >= 0.9 && angle <= 0.1 {
155                        self.api.call_action(zone, Action::ShadowDown(grp))?;
156                    } else {
157                        // we need to set a specific position and angle, this can't be done over an scene
158                        // we need to get all devices for the actual zone and show type
159                        let devices = self
160                            .zones
161                            .iter()
162                            .find(|z| z.id == zone)
163                            .ok_or("Not a valid zone id given")?
164                            .groups
165                            .iter()
166                            .find(|g| g.id == grp)
167                            .ok_or("Not a valid group given")?
168                            .devices
169                            .iter()
170                            .filter(|d| d.device_type == DeviceType::Shadow)
171                            .clone();
172
173                        for dev in devices {
174                            self.api.set_shadow_device_open(dev.id.clone(), open)?;
175                            self.api.set_shadow_device_angle(dev.id.clone(), angle)?;
176                        }
177
178                        // we need to update the state of the shadow manually, because no event will be triggered
179                        self.zones
180                            .iter_mut()
181                            .find(|z| z.id == zone)
182                            .ok_or("No valid zone given")?
183                            .groups
184                            .iter_mut()
185                            .find(|g| g.id == grp)
186                            .ok_or("Not a valid group given")?
187                            .status = value;
188                    }
189                }
190                Value::Unknown => (),
191            }
192        }
193        // when no group is defined we controll the whole zone
194        else {
195            match value {
196                // depending on the value we turn the light on or off
197                Value::Light(light) => {
198                    if light < 0.5 {
199                        self.api.call_action(zone, Action::AllLightOff)?;
200                    } else {
201                        self.api.call_action(zone, Action::AllLightOn)?;
202                    }
203                }
204                // actions need to be performed for setting the shadow
205                Value::Shadow(open, angle) => {
206                    if open <= 0.1 {
207                        self.api.call_action(zone, Action::AllShadowUp)?;
208                    }
209                    if open >= 0.9 && angle <= 0.1 {
210                        self.api.call_action(zone, Action::AllShadowDown)?;
211                    } else {
212                        // we need to set a specific position and angle, this can't be done over an scene
213
214                        // we need to get all devices for the actual zone
215                        let devices = self
216                            .zones
217                            .iter()
218                            .find(|z| z.id == zone)
219                            .ok_or("Not a valid zone id given")?
220                            .groups
221                            .iter()
222                            .map(|g| g.devices.clone())
223                            .flatten()
224                            .collect::<Vec<Device>>()
225                            .into_iter()
226                            .filter(|d| d.device_type == DeviceType::Shadow);
227
228                        for dev in devices {
229                            self.api.set_shadow_device_open(dev.id.clone(), open)?;
230                            self.api.set_shadow_device_angle(dev.id.clone(), angle)?;
231                        }
232
233                        // we need to update the state of the shadow manually, because no event will be triggered
234                        self.zones
235                            .iter_mut()
236                            .find(|z| z.id == zone)
237                            .ok_or("Not a valid zone id given")?
238                            .groups
239                            .iter_mut()
240                            .filter(|g| g.typ == Type::Shadow)
241                            .for_each(|g| g.status = value.clone());
242                    }
243                }
244                Value::Unknown => (),
245            }
246        }
247
248        Ok(())
249    }
250
251    fn expand_value(&self, event: Event) -> Result<Vec<Event>> {
252        // when we have an action of type ShadowStepOpen
253        // it effects all groups of a zone and we create multiple events for it
254        // if we have multiple groups for this typ
255        if event.typ == Type::Shadow
256            && (event.action == Action::ShadowStepOpen || event.action == Action::ShadowStepClose)
257        {
258            // we get all group id's with Shadow within the event zone
259            let groups: Vec<usize> = self
260                .zones
261                .iter()
262                .find(|z| z.id == event.zone)
263                .ok_or("No matching zone available")?
264                .groups
265                .iter()
266                .filter(|g| g.typ == Type::Shadow)
267                .map(|g| g.id)
268                .collect();
269
270            // for each shadow group we create a new event
271            return Ok(groups
272                .iter()
273                .map(|g| {
274                    let mut e = event.clone();
275                    e.group = *g;
276                    e
277                })
278                .collect());
279        }
280
281        Ok(vec![event])
282    }
283
284    fn update_event_value(&self, event: Event) -> Result<Event> {
285        // make the event mutable
286        let mut event = event;
287
288        // update the value
289        event.value = self.update_value(event.value, &event.typ, event.zone, event.group)?;
290        Ok(event)
291    }
292
293    fn update_value(&self, value: Value, typ: &Type, zone: usize, group: usize) -> Result<Value> {
294        // when the value is already defined, the event is already updated
295        if value != Value::Unknown {
296            return Ok(value);
297        }
298
299        // let's fix the shadow events
300        if typ == &Type::Shadow {
301            // get the first device for the defined group
302            let device = self
303                .zones
304                .iter()
305                .find(|z| z.id == zone)
306                .ok_or("No matching zone found")?
307                .groups
308                .iter()
309                .find(|g| g.id == group && g.typ == Type::Shadow)
310                .ok_or("No matching group found")?
311                .devices
312                .get(0)
313                .ok_or("No devices available")?;
314
315            // get the actual device values
316            let open = self.api.get_shadow_device_open(&device.id)?;
317            let angle = self.api.get_shadow_device_angle(&device.id)?;
318
319            // return them in the Shadow format
320            return Ok(Value::Shadow(open, angle));
321        }
322
323        Ok(value)
324    }
325
326    fn update_structure(&mut self) -> Result<()> {
327        let devices = self.api.get_devices()?;
328        let mut zones: Vec<Zone> = self
329            .api
330            .get_zones()?
331            .into_iter()
332            .filter(|z| z.id != 0 && z.id != 65534)
333            .collect();
334
335        for zone in &mut zones {
336            // add all the groups
337            for typ in &zone.types {
338                // get all available scenes for this zone
339                let scenes = self.api.get_scenes(zone.id, typ.clone())?;
340
341                // convert the scenes to groups
342                let mut scene_groups = Group::from_scenes(&scenes, zone.id, &typ);
343
344                // the last called action for shadows are always wrong,
345                // so we set it directly to unknown and dont request the last called scene
346                let action;
347                if typ == &Type::Shadow {
348                    action = Action::Unknown;
349                } else {
350                    // get the last called scene for this typ within a zone
351                    let lcs = self.api.get_last_called_scene(zone.id, typ.clone())?;
352                    // convert the last called scene to an action
353                    action = Action::new(typ.clone(), lcs);
354                }
355
356                // add the last called action for each scene group
357                scene_groups
358                    .iter_mut()
359                    .for_each(|g| g.status = Value::from_action(action.clone(), g.id));
360
361                // add the scene groups to the group array
362                zone.groups.append(&mut scene_groups);
363            }
364
365            // for every group add the devices
366            for group in &mut zone.groups {
367                // loop over all devices
368                // filtered down to light and shadow devices
369                for device in devices.iter().filter(|d| {
370                    d.device_type == DeviceType::Light || d.device_type == DeviceType::Shadow
371                }) {
372                    // if the device matches and the group is 0 (which means general all devices)
373                    if group.id == 0
374                        && device.zone_id == group.zone_id
375                        && group.typ == device.button_type
376                    {
377                        group.devices.push(device.clone());
378                    }
379                    // when the devices matches, but the scene group is not 0 we need to check where to sort the device
380                    else if device.zone_id == group.zone_id && group.typ == device.button_type {
381                        // check the device mode for this scene within that zone
382                        let _ = self
383                            .api
384                            .get_device_scene_mode(device.id.clone(), group.id)
385                            .and_then(|dsm| {
386                                // when the device cares about this scene group we add it
387                                if !dsm.dont_care {
388                                    group.devices.push(device.clone());
389                                }
390                                Ok(())
391                            });
392                    }
393                }
394            }
395        }
396
397        self.zones = zones.clone();
398
399        for zone in &mut zones {
400            // for every shadow group get the shadow values
401            for group in zone.groups.iter_mut().filter(|g| g.typ == Type::Shadow) {
402                // get the real shadow value
403                let status = self.update_value(group.status.clone(), &group.typ, zone.id, group.id);
404
405                // when the value available, then set it
406                match status {
407                    Ok(v) => group.status = v,
408                    Err(_) => continue,
409                }
410            }
411        }
412
413        self.zones = zones;
414
415        Ok(())
416    }
417}
418
419impl Drop for InnerAppartement {
420    fn drop(&mut self) {
421        // if it fails we can't stop the threads
422        #[allow(unused_must_use)]
423        {
424            self.thread.lock().map(|mut v| *v = false);
425        }
426    }
427}
428
429/// Raw interface towards the DSS-Rest service. This is not intend to be used
430/// directly from a API consumer. It misses important status management and
431/// abstraction over the different devices.
432#[derive(Debug, Clone)]
433pub struct RawApi {
434    host: String,
435    user: String,
436    password: String,
437    token: String,
438}
439
440impl RawApi {
441    /// Connect to the Digital Strom Server and try to login.
442    pub fn connect<S>(host: S, user: S, password: S) -> Result<Self>
443    where
444        S: Into<String>,
445    {
446        let mut api = RawApi {
447            host: host.into(),
448            user: user.into(),
449            password: password.into(),
450            token: String::from(""),
451        };
452
453        api.login()?;
454
455        Ok(api)
456    }
457
458    fn login(&mut self) -> Result<()> {
459        // build the client and allow invalid certificates
460        let client = reqwest::Client::builder()
461            .danger_accept_invalid_certs(true)
462            .build()?;
463
464        // make the login request
465        let mut response = client
466            .get(&format!("https://{}:8080/json/system/login", self.host))
467            .query(&[("user", &self.user), ("password", &self.password)])
468            .send()?;
469
470        // get the result as Json Value
471        let json: serde_json::Value = response.json()?;
472
473        // extract the token
474        self.token = json
475            .get("result")
476            .ok_or("No result in Json response")?
477            .get("token")
478            .ok_or("No token in Json response")?
479            .as_str()
480            .ok_or("Token is not a String")?
481            .to_string();
482
483        Ok(())
484    }
485
486    /// Generic requset function, which handles the token inserting/login,
487    /// the json parsing and success check.
488    ///
489    /// It returns a json value, dependet on the request.
490    pub fn generic_request<S>(
491        &self,
492        request: S,
493        parameter: Option<Vec<(&str, &str)>>,
494    ) -> Result<serde_json::Value>
495    where
496        S: Into<String>,
497    {
498        // Handle parameter and add token
499        let parameter = match parameter {
500            None => vec![("token", self.token.as_str())],
501            Some(mut p) => {
502                p.push(("token", &self.token));
503                p
504            }
505        };
506
507        // build the client and allow invalid certificates
508        let client = reqwest::Client::builder()
509            .danger_accept_invalid_certs(true)
510            .timeout(None)
511            .build()?;
512
513        // make the login request
514        let mut response = client
515            .get(&format!(
516                "https://{}:8080/json/{}",
517                self.host,
518                request.into()
519            ))
520            .query(&parameter)
521            .send()?;
522
523        let mut json: serde_json::Value = response.json()?;
524
525        // check if the response was sucessfull
526        if !json
527            .get("ok")
528            .ok_or("No ok in Json response")?
529            .as_bool()
530            .ok_or("No boolean ok code")?
531        {
532            return Err("Request failed, no ok code received".into());
533        }
534
535        // take the result and return it
536        match json.get_mut("result") {
537            None => Ok(serde_json::json!(null)),
538            Some(j) => Ok(j.take()),
539        }
540    }
541
542    /// Create a new event channel, which is listinging to events from the dss station.
543    pub fn new_event_channel(
544        &self,
545    ) -> Result<(
546        std::sync::mpsc::Receiver<Event>,
547        std::sync::Arc<std::sync::Mutex<bool>>,
548    )> {
549        // shareable boolean to stop threads
550        let thread_status = std::sync::Arc::new(std::sync::Mutex::new(true));
551
552        // subscribe to event
553        self.generic_request(
554            "event/subscribe",
555            Some(vec![("name", "callScene"), ("subscriptionID", "911")]),
556        )?;
557
558        // create a channel to send data from one to the other thread
559        let (send, recv) = std::sync::mpsc::channel();
560
561        // this thread is just receiving data and directly reconnects
562        let this = self.clone();
563        let ts = thread_status.clone();
564        std::thread::spawn(move || loop {
565            // listen for events at the server
566            let res = this.generic_request(
567                "event/get",
568                Some(vec![("timeout", "3000"), ("subscriptionID", "911")]),
569            );
570
571            // check if the thread should be ended
572            if *ts.lock().unwrap() == false {
573                break;
574            }
575
576            // we have no plan B when the sending fails
577            #[allow(unused_must_use)]
578            {
579                // send the result to the next thread
580                send.send(res);
581            }
582        });
583
584        // create a channel to send the event to thhe receiver
585        let (inp, out) = std::sync::mpsc::channel();
586
587        // this thread is processing the data and calls the event handler
588        let this = self.clone();
589        let ts = thread_status.clone();
590        std::thread::spawn(move || loop {
591            // we have no plan B when an error occours
592            #[allow(unused_must_use)]
593            {
594                // receive from the channel and continue if no channel recv error occoured
595                let res = recv.recv();
596
597                // check if the thread should be ended
598                if *ts.lock().unwrap() == false {
599                    break;
600                }
601
602                res.and_then(|res| {
603                    // continue when no reqwest (http) error occoured
604                    res.and_then(|mut v| {
605                        // extract the json into an event array
606                        this.extract_events(&mut v).and_then(|es| {
607                            // for each event call the event handler function
608                            es.into_iter().for_each(|e| {
609                                let _tmp = inp.send(e);
610                            });
611                            Ok(())
612                        });
613                        Ok(())
614                    });
615                    Ok(())
616                });
617            }
618        });
619
620        Ok((out, thread_status))
621    }
622
623    fn extract_events(&self, json: &mut serde_json::Value) -> Result<Vec<Event>> {
624        let events = json
625            .get_mut("events")
626            .ok_or("No events available")?
627            .as_array_mut()
628            .take()
629            .ok_or("Events not in array")?;
630
631        let mut out = vec![];
632
633        for e in events {
634            let name = e
635                .get("name")
636                .ok_or("No name for event")?
637                .as_str()
638                .ok_or("Event name not a string")?
639                .to_string();
640            let props = e
641                .get_mut("properties")
642                .ok_or("No properties in event")?
643                .take();
644
645            let mut event: Event = serde_json::from_value(props)?;
646            event.name = name;
647
648            event.action = Action::from(event.clone());
649
650            event.group = Group::group_id_from_scene_id(event.scene);
651
652            event.value = Value::from_action(event.action.clone(), event.group);
653
654            out.push(event);
655        }
656
657        Ok(out)
658    }
659
660    /// Receive the appartement name.
661    pub fn get_appartement_name(&self) -> Result<String> {
662        // extract the name
663        Ok(self
664            .generic_request("apartment/getName", None)?
665            .get("result")
666            .ok_or("No result in Json response")?
667            .get("name")
668            .ok_or("No name in Json response")?
669            .as_str()
670            .ok_or("Name is not a String")?
671            .to_string())
672    }
673
674    /// Set the appartement name within the DSS.
675    pub fn set_appartement_name<S>(&self, new_name: S) -> Result<bool>
676    where
677        S: Into<String>,
678    {
679        // extract the name
680        Ok(self
681            .generic_request(
682                "apartment/getName",
683                Some(vec![("newName", &new_name.into())]),
684            )?
685            .get("ok")
686            .ok_or("No ok in Json response")?
687            .as_bool()
688            .ok_or("No boolean ok code")?)
689    }
690
691    /// Request all zones from the DSS system.
692    pub fn get_zones(&self) -> Result<Vec<Zone>> {
693        let mut json = self.generic_request("apartment/getReachableGroups", None)?;
694
695        // unpack the zones
696        let json = json
697            .get_mut("zones")
698            .ok_or("No zones in Json response")?
699            .take();
700
701        // transform the data to the zones
702        Ok(serde_json::from_value(json)?)
703    }
704
705    /// Get the name of a specific zone from the DSS system.
706    pub fn get_zone_name(&self, id: usize) -> Result<String> {
707        let res = self.generic_request("zone/getName", Some(vec![("id", &id.to_string())]))?;
708
709        // unpack the name
710        let name = res
711            .get("name")
712            .ok_or("No name returned")?
713            .as_str()
714            .ok_or("No String value available")?;
715
716        Ok(name.to_string())
717    }
718
719    /// Receive all devices availble in the appartement.
720    pub fn get_devices(&self) -> Result<Vec<Device>> {
721        let res = self.generic_request("apartment/getDevices", None)?;
722
723        Ok(serde_json::from_value(res)?)
724    }
725
726    /// Request the scene mode for a specific device.
727    pub fn get_device_scene_mode<S>(&self, device: S, scene_id: usize) -> Result<SceneMode>
728    where
729        S: Into<String>,
730    {
731        let json = self.generic_request(
732            "device/getSceneMode",
733            Some(vec![
734                ("dsid", &device.into()),
735                ("sceneID", &scene_id.to_string()),
736            ]),
737        )?;
738
739        // convert to SceneMode
740        Ok(serde_json::from_value(json)?)
741    }
742
743    /// Get all available circuts
744    pub fn get_circuits(&self) -> Result<Vec<Circut>> {
745        let mut res = self.generic_request("apartment/getCircuits", None)?;
746
747        let res = res
748            .get_mut("circuits")
749            .ok_or("No circuits available")?
750            .take();
751
752        Ok(serde_json::from_value(res)?)
753    }
754
755    /// Get all available scenes for a specific zone with a type.
756    pub fn get_scenes(&self, zone: usize, typ: Type) -> Result<Vec<usize>> {
757        // convert the enum to usize
758        let typ = typ as usize;
759
760        let mut json = self.generic_request(
761            "zone/getReachableScenes",
762            Some(vec![
763                ("id", &zone.to_string()),
764                ("groupID", &typ.to_string()),
765            ]),
766        )?;
767
768        // unpack the scenes
769        let json = json
770            .get_mut("reachableScenes")
771            .ok_or("No scenes returned")?
772            .take();
773
774        // convert to number array
775        Ok(serde_json::from_value(json)?)
776    }
777
778    /// Return the last called scene for a zone.
779    pub fn get_last_called_scene(&self, zone: usize, typ: Type) -> Result<usize> {
780        // convert the enum to usize
781        let typ = typ as usize;
782
783        let res = self.generic_request(
784            "zone/getLastCalledScene",
785            Some(vec![
786                ("id", &zone.to_string()),
787                ("groupID", &typ.to_string()),
788            ]),
789        )?;
790
791        // unpack the scene
792        let number = res
793            .get("scene")
794            .ok_or("No scene returned")?
795            .as_u64()
796            .ok_or("No scene number available")?;
797
798        Ok(number as usize)
799    }
800
801    /// Trigger a scene for a specific zone and type in the dss system.
802    pub fn call_scene(&self, zone: usize, typ: Type, scene: usize) -> Result<()> {
803        // convert the enum to usize
804        let typ = typ as usize;
805
806        self.generic_request(
807            "zone/callScene",
808            Some(vec![
809                ("id", &zone.to_string()),
810                ("groupID", &typ.to_string()),
811                ("sceneNumber", &scene.to_string()),
812            ]),
813        )?;
814
815        Ok(())
816    }
817
818    /// Transforms a action to a scene call if possible and executes it
819    pub fn call_action(&self, zone: usize, action: Action) -> Result<()> {
820        // transform the action to a typ and scene
821        let (typ, scene) = action
822            .to_scene_type()
823            .ok_or("Action can't be transformed to scene command")?;
824        self.call_scene(zone, typ, scene)
825    }
826
827    /// Get the opening status of a single shadow device and resturns it.
828    pub fn get_shadow_device_open<S>(&self, device: S) -> Result<f32>
829    where
830        S: Into<String>,
831    {
832        // make the request
833        let res = self.generic_request(
834            "device/getOutputValue",
835            Some(vec![("dsid", &device.into()), ("offset", "2")]),
836        )?;
837
838        // check for the right offset
839        if res
840            .get("offset")
841            .ok_or("No offset returnes")?
842            .as_u64()
843            .ok_or("The offset is not a number")?
844            != 2
845        {
846            return Err(Error::from("Wrong offset returned"));
847        }
848
849        // extract the value
850        let value = res
851            .get("value")
852            .ok_or("No value returnes")?
853            .as_u64()
854            .ok_or("The value is not a number")?;
855
856        // get the procentage
857        let value = (value as f32) / 65535.0;
858
859        // turn the value around
860        Ok(1.0 - value)
861    }
862
863    /// Set the shadow opening for a single device
864    pub fn set_shadow_device_open<S>(&self, device: S, value: f32) -> Result<()>
865    where
866        S: Into<String>,
867    {
868        // min size is 0
869        let value = value.max(0.0);
870
871        // max size is 1
872        let value = value.min(1.0);
873
874        // move the direction 1 is down 0 is up
875        let value = 1.0 - value;
876
877        // transform to dss range
878        let value = (65535.0 * value) as usize;
879
880        // make the request
881        self.generic_request(
882            "device/setOutputValue",
883            Some(vec![
884                ("dsid", &device.into()),
885                ("value", &format!("{}", value)),
886                ("offset", "2"),
887            ]),
888        )?;
889
890        Ok(())
891    }
892
893    /// Get the shadow open angle for a single device.
894    pub fn get_shadow_device_angle<S>(&self, device: S) -> Result<f32>
895    where
896        S: Into<String>,
897    {
898        // make the request
899        let res = self.generic_request(
900            "device/getOutputValue",
901            Some(vec![("dsid", &device.into()), ("offset", "4")]),
902        )?;
903
904        // check for the right offset
905        if res
906            .get("offset")
907            .ok_or("No offset returnes")?
908            .as_u64()
909            .ok_or("The offset is not a number")?
910            != 4
911        {
912            return Err(Error::from("Wrong offset returned"));
913        }
914
915        // extract the value
916        let value = res
917            .get("value")
918            .ok_or("No value returnes")?
919            .as_u64()
920            .ok_or("The value is not a number")?;
921
922        // get the procentage
923        let value = (value as f32) / 65535.0;
924
925        Ok(value)
926    }
927
928    /// Set the shade open angle for a single device
929    pub fn set_shadow_device_angle<S>(&self, device: S, value: f32) -> Result<()>
930    where
931        S: Into<String>,
932    {
933        // min size is 0
934        let value = value.max(0.0);
935
936        // max size is 1
937        let value = value.min(1.0);
938
939        // transform to dss range
940        let value = (255.0 * value) as usize;
941
942        // make the request
943        self.generic_request(
944            "device/setOutputValue",
945            Some(vec![
946                ("dsid", &device.into()),
947                ("value", &format!("{}", value)),
948                ("offset", "4"),
949            ]),
950        )?;
951
952        Ok(())
953    }
954}
955
956/// Takes a String input and tries to convert it to the needed
957/// format requested by the struct.
958fn from_str<'de, T, D>(deserializer: D) -> std::result::Result<T, D::Error>
959where
960    T: std::str::FromStr,
961    T::Err: std::fmt::Display,
962    D: serde::Deserializer<'de>,
963{
964    use serde::Deserialize;
965
966    let s = String::deserialize(deserializer)?;
967    T::from_str(&s).map_err(serde::de::Error::custom)
968}
969
970/// The event get fired by the digital strom server, whenever
971/// a scene was called.
972///
973/// A scene get's called when a switch is pressed in the appartment or
974/// a similar action get triggered. The direct set of
975/// shadow opennings or angles are getting not received.
976#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
977pub struct Event {
978    #[serde(default)]
979    pub name: String,
980
981    #[serde(alias = "zoneID", deserialize_with = "from_str")]
982    pub zone: usize,
983
984    #[serde(alias = "groupID", deserialize_with = "from_str")]
985    pub typ: Type,
986
987    #[serde(alias = "sceneID", deserialize_with = "from_str")]
988    pub scene: usize,
989
990    #[serde(alias = "originToken")]
991    pub token: String,
992
993    #[serde(alias = "originDSUID")]
994    pub dsuid: String,
995
996    #[serde(alias = "callOrigin")]
997    pub origin: String,
998
999    #[serde(default)]
1000    pub action: Action,
1001
1002    #[serde(default)]
1003    pub value: Value,
1004
1005    #[serde(default)]
1006    pub group: usize,
1007}
1008
1009/// A zone is like a room or sub-room in an appartment.
1010/// It has a definable name and groups.
1011#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1012pub struct Zone {
1013    #[serde(alias = "zoneID")]
1014    pub id: usize,
1015    pub name: String,
1016    #[serde(alias = "groups")]
1017    pub types: Vec<Type>,
1018    #[serde(default)]
1019    pub groups: Vec<Group>,
1020}
1021
1022/// The type definition is used for a group to determine what it controlls
1023#[derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr, PartialEq, Debug, Clone)]
1024#[repr(u8)]
1025pub enum Type {
1026    Unknown = 0,
1027    Light = 1,
1028    Shadow = 2,
1029    Heating = 3,
1030    Audio = 4,
1031    Video = 5,
1032    Joker = 8,
1033    Cooling = 9,
1034    Ventilation = 10,
1035    Window = 11,
1036    AirRecirculation = 12,
1037    TemperatureControl = 48,
1038    ApartmentVentilation = 64,
1039}
1040
1041impl From<u8> for Type {
1042    /// Transform a unsigned integer representation towards a DSS Type.
1043    fn from(u: u8) -> Self {
1044        match u {
1045            1 => Type::Light,
1046            2 => Type::Shadow,
1047            3 => Type::Heating,
1048            4 => Type::Audio,
1049            5 => Type::Video,
1050            8 => Type::Joker,
1051            9 => Type::Cooling,
1052            10 => Type::Ventilation,
1053            11 => Type::Window,
1054            12 => Type::AirRecirculation,
1055            48 => Type::TemperatureControl,
1056            64 => Type::ApartmentVentilation,
1057            _ => Type::Unknown,
1058        }
1059    }
1060}
1061
1062/// This object can be directly created by serde, when a string was given.
1063impl std::str::FromStr for Type {
1064    type Err = std::num::ParseIntError;
1065
1066    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1067        let u = u8::from_str(s)?;
1068        Ok(Type::from(u))
1069    }
1070}
1071
1072/// An action defines what has happend to a specific group or what should happen.
1073#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug, Clone)]
1074pub enum Action {
1075    AllLightOn,
1076    AllLightOff,
1077    LightOn(usize),
1078    LightOff(usize),
1079    AllShadowUp,
1080    AllShadowDown,
1081    ShadowUp(usize),
1082    ShadowDown(usize),
1083    AllShadowStop,
1084    ShadowStop(usize),
1085    ShadowStepOpen,
1086    ShadowStepClose,
1087    AllShadowSpecial1,
1088    AllShadowSpecial2,
1089    Unknown,
1090}
1091
1092impl Action {
1093    fn new(typ: Type, scene: usize) -> Action {
1094        if typ == Type::Light && scene == 0 {
1095            return Action::AllLightOff;
1096        }
1097
1098        if typ == Type::Light && scene == 5 {
1099            return Action::AllLightOn;
1100        }
1101
1102        if typ == Type::Shadow && scene == 0 {
1103            return Action::AllShadowDown;
1104        }
1105
1106        if typ == Type::Shadow && scene == 5 {
1107            return Action::AllShadowUp;
1108        }
1109
1110        if scene > 0 && scene < 5 {
1111            if typ == Type::Light {
1112                return Action::LightOff(scene);
1113            } else if typ == Type::Shadow {
1114                return Action::ShadowDown(scene);
1115            }
1116        }
1117
1118        if scene > 5 && scene < 9 {
1119            if typ == Type::Light {
1120                return Action::LightOn(scene - 5);
1121            } else if typ == Type::Shadow {
1122                return Action::ShadowUp(scene - 5);
1123            }
1124        }
1125
1126        if typ == Type::Shadow && scene == 55 {
1127            return Action::AllShadowStop;
1128        }
1129
1130        if typ == Type::Shadow && scene > 50 && scene < 55 {
1131            return Action::ShadowStop(scene - 51);
1132        }
1133
1134        if typ == Type::Shadow && scene == 42 {
1135            return Action::ShadowStepClose;
1136        }
1137
1138        if typ == Type::Shadow && scene == 43 {
1139            return Action::ShadowStepOpen;
1140        }
1141
1142        if typ == Type::Shadow && scene == 17 {
1143            return Action::AllShadowUp;
1144        }
1145
1146        if typ == Type::Shadow && scene == 18 {
1147            return Action::AllShadowSpecial1;
1148        }
1149
1150        if typ == Type::Shadow && scene == 19 {
1151            return Action::AllShadowSpecial2;
1152        }
1153
1154        Action::Unknown
1155    }
1156
1157    fn to_scene_type(&self) -> Option<(Type, usize)> {
1158        match self {
1159            Action::AllLightOff => Some((Type::Light, 0)),
1160            Action::AllLightOn => Some((Type::Light, 5)),
1161            Action::LightOff(v) => Some((Type::Light, *v)),
1162            Action::LightOn(v) => Some((Type::Light, v + 5)),
1163
1164            Action::AllShadowDown => Some((Type::Shadow, 0)),
1165            Action::AllShadowUp => Some((Type::Shadow, 5)),
1166            Action::AllShadowStop => Some((Type::Shadow, 55)),
1167            Action::AllShadowSpecial1 => Some((Type::Shadow, 18)),
1168            Action::AllShadowSpecial2 => Some((Type::Shadow, 19)),
1169
1170            Action::ShadowDown(v) => Some((Type::Shadow, *v)),
1171            Action::ShadowUp(v) => Some((Type::Shadow, v + 5)),
1172            Action::ShadowStop(v) => Some((Type::Shadow, v + 51)),
1173            Action::ShadowStepClose => Some((Type::Shadow, 42)),
1174            Action::ShadowStepOpen => Some((Type::Shadow, 43)),
1175
1176            Action::Unknown => None,
1177        }
1178    }
1179}
1180
1181impl Default for Action {
1182    fn default() -> Self {
1183        Action::Unknown
1184    }
1185}
1186
1187impl From<Event> for Action {
1188    fn from(e: Event) -> Self {
1189        Action::new(e.typ, e.scene)
1190    }
1191}
1192
1193/// The Value objects describes which status a group has. It is also used to
1194/// set the new status of a group.
1195#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
1196pub enum Value {
1197    Light(f32),
1198    Shadow(f32, f32),
1199    Unknown,
1200}
1201
1202impl Value {
1203    pub fn from_action(action: Action, _id: usize) -> Self {
1204        match action {
1205            Action::AllLightOn => Value::Light(1.0),
1206            Action::LightOn(_id) => Value::Light(1.0),
1207            Action::AllLightOff => Value::Light(0.0),
1208            Action::LightOff(_id) => Value::Light(0.0),
1209            Action::AllShadowUp => Value::Shadow(0.0, 1.0),
1210            Action::AllShadowDown => Value::Shadow(1.0, 0.0),
1211            Action::ShadowDown(_id) => Value::Shadow(1.0, 0.0),
1212            Action::ShadowUp(_id) => Value::Shadow(0.0, 1.0),
1213            _ => Value::Unknown,
1214        }
1215    }
1216}
1217
1218impl Default for Value {
1219    fn default() -> Self {
1220        Value::Unknown
1221    }
1222}
1223
1224/// A specific device which is used within a group
1225#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1226pub struct Device {
1227    pub id: String,
1228    pub name: String,
1229    #[serde(alias = "zoneID")]
1230    pub zone_id: usize,
1231    #[serde(alias = "isPresent")]
1232    pub present: bool,
1233    #[serde(alias = "outputMode")]
1234    pub device_type: DeviceType,
1235    #[serde(alias = "groups")]
1236    pub types: Vec<Type>,
1237    #[serde(alias = "buttonActiveGroup")]
1238    pub button_type: Type,
1239}
1240
1241/// The device type describes, what kind of device is avilable.
1242#[derive(Debug, Clone, serde::Serialize, PartialEq)]
1243pub enum DeviceType {
1244    Switch,
1245    Light,
1246    Tv,
1247    Shadow,
1248    Unknown,
1249}
1250
1251impl From<usize> for DeviceType {
1252    fn from(num: usize) -> Self {
1253        match num {
1254            0 => DeviceType::Switch,
1255            16 | 22 | 35 => DeviceType::Light,
1256            33 => DeviceType::Shadow,
1257            39 => DeviceType::Tv,
1258            _ => DeviceType::Unknown,
1259        }
1260    }
1261}
1262
1263impl<'de> serde::Deserialize<'de> for DeviceType {
1264    fn deserialize<D>(deserializer: D) -> std::result::Result<DeviceType, D::Error>
1265    where
1266        D: serde::Deserializer<'de>,
1267    {
1268        Ok(DeviceType::from(usize::deserialize(deserializer)?))
1269    }
1270}
1271
1272/// A circut is a device which provides meter functionality within a dss installation.
1273#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1274pub struct Circut {
1275    #[serde(alias = "dsid")]
1276    pub id: String,
1277    pub name: String,
1278    #[serde(alias = "isPresent")]
1279    pub present: bool,
1280    #[serde(alias = "isValid")]
1281    pub valid: bool,
1282}
1283
1284/// Represents all special scene stats.
1285#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1286pub struct SceneMode {
1287    #[serde(alias = "sceneID")]
1288    pub scene: usize,
1289    #[serde(alias = "dontCare")]
1290    pub dont_care: bool,
1291    #[serde(alias = "localPrio")]
1292    pub local_prio: bool,
1293    #[serde(alias = "specialMode")]
1294    pub special_mode: bool,
1295    #[serde(alias = "flashMode")]
1296    pub flash_mode: bool,
1297    #[serde(alias = "ledconIndex")]
1298    pub led_con_index: usize,
1299}
1300
1301/// A Group which is located in a zone and holds all the single devices
1302#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1303pub struct Group {
1304    id: usize,
1305    zone_id: usize,
1306    typ: Type,
1307    status: Value,
1308    devices: Vec<Device>,
1309}
1310
1311impl Group {
1312    pub fn new(id: usize, zone_id: usize, typ: Type) -> Self {
1313        Group {
1314            id,
1315            zone_id: zone_id,
1316            typ,
1317            devices: vec![],
1318            status: Value::default(),
1319        }
1320    }
1321
1322    pub fn group_id_from_scene_id(scene: usize) -> usize {
1323        if scene > 0 && scene < 5 {
1324            return scene;
1325        }
1326
1327        if scene > 5 && scene < 9 {
1328            return scene - 5;
1329        }
1330
1331        if scene > 50 && scene < 55 {
1332            return scene - 51;
1333        }
1334
1335        0
1336    }
1337
1338    pub fn from_scene(scene: usize, zone_id: usize, typ: &Type) -> Option<Group> {
1339        // add the different scene groups if they exist
1340        for x in 1..4 {
1341            if scene == x {
1342                return Some(Group::new(x, zone_id, typ.clone()));
1343            }
1344        }
1345
1346        // if no different scene groups availabe, we take the general one
1347        if scene == 0 {
1348            return Some(Group::new(0, zone_id, typ.clone()));
1349        }
1350
1351        None
1352    }
1353
1354    pub fn from_scenes(scenes: &[usize], zone_id: usize, typ: &Type) -> Vec<Group> {
1355        let groups: Vec<Group> = scenes
1356            .iter()
1357            .filter_map(|s| Group::from_scene(*s, zone_id, typ))
1358            .collect();
1359
1360        if groups.len() > 1 {
1361            return groups.into_iter().filter(|g| g.id > 0).collect();
1362        }
1363
1364        return groups;
1365    }
1366}
1367
1368impl Default for Group {
1369    fn default() -> Self {
1370        Group {
1371            id: 0,
1372            zone_id: 0,
1373            typ: Type::Unknown,
1374            devices: vec![],
1375            status: Value::default(),
1376        }
1377    }
1378}
1379
1380/// DSS error type to collect all the avilable errors which can occour.
1381#[derive(Debug)]
1382pub enum Error {
1383    Error(String),
1384    SerdeJson(serde_json::Error),
1385    Reqwest(reqwest::Error),
1386}
1387
1388/// Short return type for the DSS Error
1389type Result<T> = std::result::Result<T, Error>;
1390
1391impl std::fmt::Display for Error {
1392    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1393        match self {
1394            Error::Error(s) => write!(f, "{}", s),
1395            Error::SerdeJson(ref e) => e.fmt(f),
1396            Error::Reqwest(ref e) => e.fmt(f),
1397        }
1398    }
1399}
1400
1401impl std::error::Error for Error {
1402    fn cause(&self) -> Option<&dyn std::error::Error> {
1403        match self {
1404            Error::Error(_) => None,
1405            Error::SerdeJson(ref e) => Some(e),
1406            Error::Reqwest(ref e) => Some(e),
1407        }
1408    }
1409}
1410
1411// immplement error from string
1412impl From<&str> for Error {
1413    fn from(err: &str) -> Self {
1414        Error::Error(err.into())
1415    }
1416}
1417
1418// immplement Serde Json
1419impl From<serde_json::Error> for Error {
1420    fn from(err: serde_json::Error) -> Self {
1421        Error::SerdeJson(err)
1422    }
1423}
1424
1425// immplement Serde Json
1426impl From<reqwest::Error> for Error {
1427    fn from(err: reqwest::Error) -> Self {
1428        Error::Reqwest(err)
1429    }
1430}
1431
1432// implement mutex poison error
1433impl<T> From<std::sync::PoisonError<T>> for Error {
1434    fn from(_err: std::sync::PoisonError<T>) -> Self {
1435        Error::from("Poison error")
1436    }
1437}