enet_client/
lib.rs

1//! Client for interacting with eNet gateways.
2
3macro_rules! bail {
4  ($err:expr) => {
5    return Err($err.into())
6  };
7}
8
9pub mod cmd;
10mod conn;
11pub mod dev;
12mod enc;
13mod evt;
14mod room;
15
16use std::convert::TryFrom;
17
18pub use cmd::SetValuesCommandError;
19pub use conn::ConnectError;
20use dev::DeviceState;
21pub use dev::{BinaryDevice, Device, DimmerDevice, EnetDevice};
22pub use enet_proto::{ClickDuration, ItemSetValue, ItemValueRes, SetValue};
23
24use crate::{dev::DeviceDesc, room::RoomDesc};
25use cmd::CommandHandler;
26use evt::EventHandler;
27use thiserror::Error;
28use tokio::net::ToSocketAddrs;
29use tracing::{event, instrument, Level};
30
31pub struct EnetClient {
32  #[allow(dead_code)]
33  commands: CommandHandler,
34  #[allow(dead_code)]
35  events: EventHandler,
36  #[allow(dead_code)]
37  rooms: Vec<RoomDesc>,
38  devices: Vec<Device>,
39}
40
41impl EnetClient {
42  #[instrument(level = "info", target = "enet-client", skip(addr), err)]
43  pub async fn new<A>(addr: A) -> Result<Self, ClientConnectError>
44  where
45    A: ToSocketAddrs + Clone + Send + Sync + 'static,
46  {
47    let mut commands = CommandHandler::new(addr.clone()).await?;
48    let version = commands.get_version().await?;
49    event!(target: "enet-client", Level::INFO, %version.firmware, %version.hardware, %version.enet, "connected to eNet Gateway");
50
51    let channel_types = commands.get_channel_info().await?;
52    let project = commands.get_project().await?;
53    let rooms = project
54      .lists
55      .into_iter()
56      .filter(|l| l.visible)
57      .map(RoomDesc::from)
58      .collect::<Vec<_>>();
59
60    let (writers, devices) = project
61      .items
62      .into_iter()
63      .enumerate()
64      .filter(|(idx, _)| channel_types.devices.get(*idx) == Some(&1))
65      .filter_map(|(idx, item)| DeviceDesc::try_from(item).ok().map(|v| (idx, v)))
66      .map(|(idx, desc)| Device::new(desc, idx as u32))
67      .unzip();
68
69    let devices: Vec<_> = devices;
70    event!(target: "enet-client", Level::INFO, rooms.len = %rooms.len(), devices.len = %devices.len(), "got project info");
71
72    let events = EventHandler::new(addr, writers).await?;
73
74    Ok(Self {
75      commands,
76      events,
77      rooms,
78      devices,
79    })
80  }
81
82  pub fn devices(&self) -> &[Device] {
83    &self.devices
84  }
85
86  pub fn device(&self, number: u32) -> Option<&Device> {
87    self.devices.iter().find(|d| d.number() == number)
88  }
89
90  pub async fn set_value(
91    &mut self,
92    number: u32,
93    value: SetValue,
94  ) -> Result<(), SetValuesCommandError> {
95    let values = vec![ItemSetValue { number, value }];
96    self.set_values(values).await
97  }
98
99  pub async fn set_values(
100    &mut self,
101    values: impl IntoIterator<Item = ItemSetValue>,
102  ) -> Result<(), SetValuesCommandError> {
103    let values: Vec<ItemSetValue> = values.into_iter().collect();
104    let new_states = values
105      .iter()
106      .map(|v| (v.number, DeviceState::from(v.value)))
107      .collect();
108
109    self.commands.set_values(values).await?;
110    let _ = self.events.update_values(new_states);
111
112    Ok(())
113  }
114}
115
116#[non_exhaustive]
117#[derive(Debug, Error)]
118#[error("Failed to connect to gateway.")]
119pub enum ClientConnectError {
120  Connect(#[from] ConnectError),
121  GetVersionCommand(#[from] cmd::GetVersionCommandError),
122  GetChannelInfoCommand(#[from] cmd::GetChannelInfoCommandError),
123  GetProjectCommand(#[from] cmd::GetProjectCommandError),
124}