1macro_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}