tinkerforge_async/
device.rs1use 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#[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#[derive(Debug, Copy, Clone)]
71pub enum SetResponseExpectedError {
72 InvalidFunctionId(u8),
74 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}