tinkerforge_async/
device.rs

1//! Generic device functionality which is used by all bricks and bricklets.
2
3use std::time::Duration;
4
5use futures_core::Stream;
6#[cfg(feature = "prometheus")]
7use lazy_static::lazy_static;
8#[cfg(feature = "prometheus")]
9use prometheus::{register_histogram_vec, HistogramVec};
10
11use crate::{
12    base58::Uid,
13    error::TinkerforgeError,
14    ip_connection::async_io::{AsyncIpConnection, PacketData},
15};
16
17#[cfg(feature = "prometheus")]
18lazy_static! {
19    static ref REQUEST_TIMING: HistogramVec = register_histogram_vec!(
20        "tinkerforge_request",
21        "The Tinkerforge response times latencies in seconds.",
22        &["device_type", "function_id", "method"]
23    )
24    .unwrap();
25}
26
27#[derive(Debug, Copy, Clone, PartialEq)]
28pub(crate) enum ResponseExpectedFlag {
29    InvalidFunctionId,
30    False,
31    True,
32    AlwaysTrue,
33}
34
35impl From<bool> for ResponseExpectedFlag {
36    fn from(b: bool) -> Self {
37        if b {
38            ResponseExpectedFlag::True
39        } else {
40            ResponseExpectedFlag::False
41        }
42    }
43}
44
45const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
46
47#[derive(Clone)]
48pub(crate) struct Device {
49    pub api_version: [u8; 3],
50    pub response_expected: [ResponseExpectedFlag; 256],
51    pub internal_uid: Uid,
52    pub connection: AsyncIpConnection,
53    #[cfg(feature = "prometheus")]
54    device_display_name: &'static str,
55}
56
57/// This error is returned if the response expected status was queried for an unknown function.
58#[derive(Debug, Copy, Clone)]
59pub struct GetResponseExpectedError(u8);
60
61impl std::error::Error for GetResponseExpectedError {}
62
63impl std::fmt::Display for GetResponseExpectedError {
64    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
65        write!(f, "Can not get response expected: Invalid function id {}", self.0)
66    }
67}
68
69/// This error is returned if the response expected status of a function could not be changed.
70#[derive(Debug, Copy, Clone)]
71pub enum SetResponseExpectedError {
72    /// The function id was unknown. Maybe the wrong UID was used?
73    InvalidFunctionId(u8),
74    /// This function must always respond, as the response contains result data.
75    IsAlwaysTrue(u8),
76}
77
78impl std::error::Error for SetResponseExpectedError {}
79
80impl std::fmt::Display for SetResponseExpectedError {
81    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82        match self {
83            SetResponseExpectedError::InvalidFunctionId(fid) => write!(f, "Can not set response expected: Invalid function id {}", fid),
84            SetResponseExpectedError::IsAlwaysTrue(_fid) => write!(f, "Can not set response expected: function always responds."),
85        }
86    }
87}
88
89impl Device {
90    pub(crate) fn new(
91        api_version: [u8; 3],
92        internal_uid: Uid,
93        connection: AsyncIpConnection,
94        #[allow(unused)] device_display_name: &'static str,
95    ) -> Device {
96        Device {
97            api_version,
98            internal_uid,
99            response_expected: [ResponseExpectedFlag::InvalidFunctionId; 256],
100            connection,
101            #[cfg(feature = "prometheus")]
102            device_display_name,
103        }
104    }
105
106    pub(crate) fn get_response_expected(&self, function_id: u8) -> Result<bool, GetResponseExpectedError> {
107        match self.response_expected[function_id as usize] {
108            ResponseExpectedFlag::False => Ok(false),
109            ResponseExpectedFlag::True => Ok(true),
110            ResponseExpectedFlag::AlwaysTrue => Ok(true),
111            ResponseExpectedFlag::InvalidFunctionId => Err(GetResponseExpectedError(function_id)),
112        }
113    }
114
115    pub(crate) fn set_response_expected(&mut self, function_id: u8, response_expected: bool) -> Result<(), SetResponseExpectedError> {
116        if self.response_expected[function_id as usize] == ResponseExpectedFlag::AlwaysTrue {
117            Err(SetResponseExpectedError::IsAlwaysTrue(function_id))
118        } else if self.response_expected[function_id as usize] == ResponseExpectedFlag::InvalidFunctionId {
119            Err(SetResponseExpectedError::InvalidFunctionId(function_id))
120        } else {
121            self.response_expected[function_id as usize] = ResponseExpectedFlag::from(response_expected);
122            Ok(())
123        }
124    }
125
126    pub(crate) fn set_response_expected_all(&mut self, response_expected: bool) {
127        for resp_exp in self.response_expected.iter_mut() {
128            if *resp_exp == ResponseExpectedFlag::True || *resp_exp == ResponseExpectedFlag::False {
129                *resp_exp = ResponseExpectedFlag::from(response_expected);
130            }
131        }
132    }
133
134    pub(crate) async fn set(&mut self, function_id: u8, payload: &[u8]) -> Result<Option<PacketData>, TinkerforgeError> {
135        #[cfg(feature = "prometheus")]
136        let timer = REQUEST_TIMING.with_label_values(&[self.device_display_name, function_id.to_string().as_str(), "set"]).start_timer();
137        let timeout =
138            if self.response_expected[function_id as usize] == ResponseExpectedFlag::False { None } else { Some(DEFAULT_TIMEOUT) };
139        let result = self.connection.set(self.internal_uid, function_id, payload, timeout).await;
140        #[cfg(feature = "prometheus")]
141        drop(timer);
142        result
143    }
144
145    pub(crate) async fn get_callback_receiver(&mut self, function_id: u8) -> impl Stream<Item = PacketData> {
146        self.connection.callback_stream(self.internal_uid, function_id).await
147    }
148
149    pub(crate) async fn get(&mut self, function_id: u8, payload: &[u8]) -> Result<PacketData, TinkerforgeError> {
150        #[cfg(feature = "prometheus")]
151        let timer = REQUEST_TIMING.with_label_values(&[self.device_display_name, function_id.to_string().as_str(), "get"]).start_timer();
152        let result = self.connection.get(self.internal_uid, function_id, payload, DEFAULT_TIMEOUT).await;
153        #[cfg(feature = "prometheus")]
154        drop(timer);
155        result
156    }
157}