Skip to main content

edgehog_device_runtime/telemetry/
event.rs

1// This file is part of Edgehog.
2//
3// Copyright 2024 SECO Mind Srl
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// SPDX-License-Identifier: Apache-2.0
18
19//! Telemetry event from Astarte.
20
21use std::time::Duration;
22
23use astarte_device_sdk::{
24    event::FromEventError, types::TypeError, AstarteData, DeviceEvent, FromEvent,
25};
26use tracing::warn;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct TelemetryEvent {
30    pub interface: String,
31    pub config: TelemetryConfig,
32}
33
34impl FromEvent for TelemetryEvent {
35    type Err = FromEventError;
36
37    fn from_event(event: DeviceEvent) -> Result<Self, Self::Err> {
38        let interface = TelemetryConfig::interface_from_path(&event.path).ok_or_else(|| {
39            FromEventError::Path {
40                interface: "io.edgehog.devicemanager.config.Telemetry",
41                base_path: event.path.clone(),
42            }
43        })?;
44
45        TelemetryConfig::from_event(event).map(|config| TelemetryEvent { interface, config })
46    }
47}
48
49#[derive(Debug, Clone, FromEvent, PartialEq, Eq)]
50#[from_event(
51    interface = "io.edgehog.devicemanager.config.Telemetry",
52    interface_type = "properties",
53    aggregation = "individual"
54)]
55pub enum TelemetryConfig {
56    #[mapping(endpoint = "/request/%{interface_name}/enable", allow_unset = true)]
57    Enable(Option<bool>),
58    #[mapping(
59        endpoint = "/request/%{interface_name}/periodSeconds",
60        allow_unset = true
61    )]
62    Period(Option<TelemetryPeriod>),
63}
64
65impl TelemetryConfig {
66    fn interface_from_path(path: &str) -> Option<String> {
67        path.strip_prefix('/')
68            .and_then(|path| path.split('/').nth(1))
69            .map(str::to_string)
70    }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct TelemetryPeriod(pub Duration);
75
76impl TryFrom<AstarteData> for TelemetryPeriod {
77    type Error = TypeError;
78
79    fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
80        let secs = i64::try_from(value)?;
81        let secs = u64::try_from(secs).unwrap_or_else(|_| {
82            warn!("Telemetry period seconds value too big {secs}, capping to u64::MAX");
83
84            u64::MAX
85        });
86
87        Ok(Self(Duration::from_secs(secs)))
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use astarte_device_sdk::Value;
94
95    use crate::controller::event::RuntimeEvent;
96
97    use super::*;
98
99    #[test]
100    fn should_convert_telemetry_from_event() {
101        let event = DeviceEvent {
102            interface: "io.edgehog.devicemanager.config.Telemetry".to_string(),
103            path: "/request/foo/enable".to_string(),
104            data: Value::Property(Some(true.into())),
105        };
106
107        let res = RuntimeEvent::from_event(event).unwrap();
108
109        assert_eq!(
110            res,
111            RuntimeEvent::Telemetry(TelemetryEvent {
112                interface: "foo".to_string(),
113                config: TelemetryConfig::Enable(Some(true))
114            })
115        );
116
117        let event = DeviceEvent {
118            interface: "io.edgehog.devicemanager.config.Telemetry".to_string(),
119            path: "/request/foo/periodSeconds".to_string(),
120            data: Value::Property(Some(AstarteData::LongInteger(42))),
121        };
122
123        let res = RuntimeEvent::from_event(event).unwrap();
124
125        assert_eq!(
126            res,
127            RuntimeEvent::Telemetry(TelemetryEvent {
128                interface: "foo".to_string(),
129                config: TelemetryConfig::Period(Some(TelemetryPeriod(Duration::from_secs(42))))
130            })
131        );
132    }
133}