uhppote_rs/
lib.rs

1#![doc(html_root_url = "https://docs.rs/uhppote-rs/1/")]
2//! uhppote-rs is a safe Rust library for access control systems based on the UHPPOTE UT0311-L0x
3//! TCP/IP Wiegand access control boards. This library is based on the
4//! [uhppoted](https://github.com/uhppoted/uhppoted) project.
5//!
6//! Most interactions with the system happen through the [`Device`] type.
7//!
8//! Example:
9//! ```no_run
10//! use uhppote_rs::Uhppote;
11//! let uhppoted = Uhppoted::default();
12//! let device = uhppoted.get_device(423196779).unwrap();
13//! let status = device.get_status().unwrap();
14//! ```
15mod messages;
16mod types;
17use anyhow::bail;
18use anyhow::Result;
19use chrono::Datelike;
20pub use chrono::NaiveDate;
21pub use chrono::NaiveDateTime;
22pub use chrono::NaiveTime;
23use messages::types::DateBCD;
24use messages::*;
25use std::fmt::Debug;
26use std::net::Ipv4Addr;
27use std::net::SocketAddr;
28use std::net::UdpSocket;
29use std::time::Duration;
30pub use types::*;
31
32const UHPPOTE_PORT: u16 = 60000;
33#[derive(Debug)]
34pub struct Uhppoted {
35    bind_address: SocketAddr,
36    broadcast_address: Ipv4Addr,
37    timeout: Duration,
38}
39
40impl Uhppoted {
41    /// Create a new Uhppote struct
42    ///
43    /// Example:
44    /// ```no_run
45    /// use uhppote_rs::Uhppoted;
46    /// let uhppoted = UUhppoted::new(
47    ///     "0.0.0.0:0".parse().unwrap(),
48    ///     "255.255.255.255".parse().unwrap(),
49    ///     Duration::new(5, 0),
50    ///     Vec::new(),
51    ///     false,
52    /// )
53    pub fn new(bind: SocketAddr, broadcast: Ipv4Addr, timeout: Duration) -> Uhppoted {
54        Uhppoted {
55            bind_address: bind,
56            broadcast_address: broadcast,
57            timeout,
58        }
59    }
60
61    /// Get all the available [`DeviceConfig`]s on the local network. This broadcasts a discovery message
62    /// and waits [`Uhppoted::timeout`] for responses.
63    pub fn get_device_configs(&self) -> Result<Vec<DeviceConfig>> {
64        let request = GetConfigRequest::new(0);
65        let response: Vec<GetConfigResponse> = broadcast_and_receive(request, self)?;
66        let r = response
67            .into_iter()
68            .map(|r| r.try_into().unwrap())
69            .collect();
70        Ok(r)
71    }
72
73    /// Get all the available [`Device`]s on the local network. This broadcasts a discovery message
74    /// and waits [`Uhppoted::timeout`] for responses.
75    pub fn get_devices(&self) -> Result<Vec<Device>> {
76        let request = GetConfigRequest::new(0);
77        let response: Vec<GetConfigResponse> = broadcast_and_receive(request, self)?;
78        let r = response
79            .into_iter()
80            .map(|r| Device::new(self, r.device_id, Some(r.ip_address)))
81            .collect();
82        Ok(r)
83    }
84
85    /// Get a [`Device`] by its device ID. This does not check if the device actually exists, but
86    /// merely represents a device to interact with.
87    ///
88    /// When `ip_address` is specified, communication will happen directly with the device. Otherwise,
89    /// communication to the device will happen via local network broadcast.
90    ///
91    /// Specify an `ip_address` when the device is not on the local network.
92    pub fn get_device(&self, id: u32, ip_address: Option<Ipv4Addr>) -> Device {
93        Device::new(self, id, ip_address)
94    }
95
96    /// Listen for incoming [`Status`] messages from the UHPPOTE system on a specific `address`.
97    /// Example:
98    /// ```no_run
99    /// use uhppote_rs::Uhppoted;
100    /// let uhppoted = Uhppoted::default();
101    /// let device = uhppoted.get_device(423196779);
102    ///
103    /// let listener_address: SocketAddr = "192.168.0.10:12345".parse().unwrap();
104    ///
105    /// device.set_listener(listener_address).unwrap();
106    /// uhppoted.listen(listener_address, |status| {
107    ///     println!("{:?}", status);
108    /// });
109    /// ```
110    pub fn listen(&self, address: SocketAddr, handler: fn(Status)) -> Result<()> {
111        let socket = UdpSocket::bind(&address)?;
112        socket.set_broadcast(true)?;
113        socket.set_read_timeout(None)?;
114        loop {
115            let mut buf = [0u8; 64];
116            socket.recv(&mut buf)?;
117            match buf[1].try_into()? {
118                RequestResponseType::Status => {
119                    let response = GetStatusResponse::from_bytes(&buf)?;
120                    handler(response.try_into()?);
121                }
122                response_type => bail!("Can't listen for {:?}", response_type),
123            }
124        }
125    }
126}
127
128impl Default for Uhppoted {
129    /// Creates a default instance of [`Uhppoted`].
130    /// Defaults:
131    ///   - `bind`: 0.0.0.0:0
132    ///   - `broadcast`: 255.255.255.255
133    ///   - `listen`: None
134    ///   - `timeout`: Duration::new(5, 0)
135    ///   - `controllers`: None
136    fn default() -> Uhppoted {
137        Uhppoted::new(
138            "0.0.0.0:0".parse().unwrap(),
139            "255.255.255.255".parse().unwrap(),
140            Duration::new(5, 0),
141        )
142    }
143}
144
145#[derive(Debug)]
146pub struct Device<'a> {
147    u: &'a Uhppoted,
148    id: u32,
149    ip_address: Option<Ipv4Addr>,
150}
151
152impl<'a> Device<'a> {
153    /// Create a new [`Device`] from an [`Uhppoted`] and a device ID.
154    fn new(u: &'a Uhppoted, id: u32, ip_address: Option<Ipv4Addr>) -> Device<'a> {
155        Device { u, id, ip_address }
156    }
157
158    /// Add a [`Card`] to the [`Device`].
159    pub fn add_card(&self, card: Card) -> Result<()> {
160        let request = PutCardRequest::new(
161            self.id,
162            card.number,
163            card.from.try_into()?,
164            card.to.try_into()?,
165            card.doors[0],
166            card.doors[1],
167            card.doors[2],
168            card.doors[3],
169        );
170        let response: PutCardResponse = send_and_receive(request, self)?;
171        if response.success {
172            Ok(())
173        } else {
174            bail!("PutCard failed")
175        }
176    }
177
178    /// Add a [`Task`] to the system.
179    pub fn add_task(&self, task: Task) -> Result<()> {
180        let request = AddTaskRequest::new(
181            self.id,
182            DateBCD::new(
183                task.from.year() as u16,
184                task.from.month() as u8,
185                task.from.day() as u8,
186            ),
187            DateBCD::new(
188                task.to.year() as u16,
189                task.to.month() as u8,
190                task.to.day() as u8,
191            ),
192            task.monday,
193            task.tuesday,
194            task.wednesday,
195            task.thursday,
196            task.friday,
197            task.saturday,
198            task.sunday,
199            task.at.try_into()?,
200            task.door,
201            task.task as u8,
202            task.more_cards,
203        );
204
205        let response: AddTaskResponse = send_and_receive(request, self)?;
206
207        if response.success {
208            Ok(())
209        } else {
210            bail!("AddTask failed")
211        }
212    }
213
214    /// Remove all [`Card`]s from the [`Device`].
215    pub fn clear_cards(&self) -> Result<()> {
216        let magic_word = 0x55aaaa55;
217        let request = DeleteCardsRequest::new(self.id, magic_word);
218        let response: DeleteCardsResponse = send_and_receive(request, self)?;
219        if response.success {
220            Ok(())
221        } else {
222            bail!("DeleteCard failed")
223        }
224    }
225
226    /// Remove all [`Task`]s from the [`Device`].
227    pub fn clear_tasks(&self) -> Result<()> {
228        let request = ClearTaskListRequest::new(self.id, 0x55aaaa55);
229        let response: ClearTaskListResponse = send_and_receive(request, self)?;
230        if response.success {
231            Ok(())
232        } else {
233            bail!("ClearTaskList failed")
234        }
235    }
236
237    /// Remove all [`TimeProfile`]s from the [`Device`].
238    pub fn clear_time_profiles(&self) -> Result<()> {
239        let magic_word = 0x55aaaa55;
240        let request = ClearTimeProfilesRequest::new(self.id, magic_word);
241        let response: ClearTimeProfilesResponse = send_and_receive(request, self)?;
242        if response.magic_word == magic_word {
243            Ok(())
244        } else {
245            bail!("ClearTimeProfiles failed")
246        }
247    }
248
249    /// Remove a [`Card`] from the [`Device`].
250    pub fn delete_card(&self, number: u32) -> Result<()> {
251        let request = DeleteCardRequest::new(self.id, number);
252        let response: DeleteCardResponse = send_and_receive(request, self)?;
253        if response.success {
254            Ok(())
255        } else {
256            bail!("DeleteCard failed")
257        }
258    }
259
260    /// Get a specific [`Card`] by its ID.
261    pub fn get_card_by_id(&self, id: u32) -> Result<Card> {
262        let request = GetCardByIDRequest::new(self.id, id);
263        let response: GetCardByIDResponse = send_and_receive(request, self)?;
264        response.try_into()
265    }
266
267    /// Get a specific [`Card`] by its index.
268    pub fn get_card_by_index(&self, index: u32) -> Result<Card> {
269        let request = GetCardByIndexRequest::new(self.id, index);
270        let response: GetCardByIndexResponse = send_and_receive(request, self)?;
271        response.try_into()
272    }
273
274    /// Get the number of [`Card`]s from the [`Device`].
275    pub fn get_cards(&self) -> Result<u32> {
276        let request = GetCardsRequest::new(self.id);
277        let response: GetCardsResponse = send_and_receive(request, self)?;
278        Ok(response.records)
279    }
280
281    /// Get a [`DeviceConfig`] for a the [`Device`].
282    pub fn get_config(&self) -> Result<DeviceConfig> {
283        let request = GetConfigRequest::new(self.id);
284        let response: GetConfigResponse = send_and_receive(request, self)?;
285        response.try_into()
286    }
287
288    /// Get a [`DoorControl`] for a specific door.
289    /// Note that doors are addressed 1-4, not 0-3.
290    pub fn get_door_control(&self, door: u8) -> Result<DoorControl> {
291        let request = GetDoorControlStateRequest::new(self.id, door);
292        let response: GetDoorControlStateResponse = send_and_receive(request, self)?;
293        Ok(response.into())
294    }
295
296    /// Get an [`Event`] by its index.
297    pub fn get_event(&self, index: u32) -> Result<Event> {
298        let request = GetEventRequest::new(self.id, index);
299        let response: GetEventResponse = send_and_receive(request, self)?;
300        response.try_into()
301    }
302
303    /// Get the event index the [`Device`]
304    pub fn get_event_index(&self) -> Result<u32> {
305        let request = GetEventIndexRequest::new(self.id);
306        let response: GetEventIndexResponse = send_and_receive(request, self)?;
307        Ok(response.index)
308    }
309
310    /// Get what listener (IP:PORT) is set on the [`Device`]. This is where the the [`Device`]
311    /// will send [`Status`] messages to over UDP.
312    pub fn get_listener(&self) -> Result<SocketAddr> {
313        let request = GetListenerRequest::new(self.id);
314        let response: GetListenerResponse = send_and_receive(request, self)?;
315        Ok(SocketAddr::from((response.ip_address, response.port)))
316    }
317
318    /// Get the [`Status`] of the [`Device`].
319    pub fn get_status(&self) -> Result<Status> {
320        let request = GetStatusRequest::new(self.id);
321        let response: GetStatusResponse = send_and_receive(request, self)?;
322        let status: Status = response.try_into()?;
323        Ok(status)
324    }
325
326    /// Get the current time of the [`Device`].
327    pub fn get_time(&self) -> Result<NaiveDateTime> {
328        let request = GetTimeRequest::new(self.id);
329        let response: GetTimeResponse = send_and_receive(request, self)?;
330        response.datetime.try_into()
331    }
332
333    /// Get the [`TimeProfile`] by ID.
334    pub fn get_time_profile(&self, profile_id: u8) -> Result<TimeProfile> {
335        let request = GetTimeProfileRequest::new(self.id, profile_id);
336        let response: GetTimeProfileResponse = send_and_receive(request, self)?;
337        response.try_into()
338    }
339
340    /// Open a door.
341    /// Note that doors are addressed 1-4, not 0-3.
342    pub fn open_door(&self, door: u8) -> Result<()> {
343        let request = OpenDoorRequest::new(self.id, door);
344        let response: OpenDoorResponse = send_and_receive(request, self)?;
345        if response.success {
346            Ok(())
347        } else {
348            bail!("OpenDoor failed")
349        }
350    }
351
352    /// Refresh the task list of the [`Device`].
353    pub fn refresh_task_list(&self) -> Result<()> {
354        let request = RefreshTaskListRequest::new(self.id, 0x55aaaa55);
355        let response: RefreshTaskListResponse = send_and_receive(request, self)?;
356        if response.success {
357            Ok(())
358        } else {
359            bail!("RefreshTaskList failed")
360        }
361    }
362
363    /// Set the [`DoorControl`] for a specific door.
364    /// Note that the delay is in seconds and can maximally be 255.
365    pub fn set_door_control_state(&self, door: u8, state: DoorControl) -> Result<DoorControl> {
366        let request = SetDoorControlStateRequest::new(
367            self.id,
368            door,
369            state.mode as u8,
370            state.delay.as_secs() as u8,
371        );
372        let response: SetDoorControlStateResponse = send_and_receive(request, self)?;
373        Ok(response.into())
374    }
375
376    /// Set the event index the [`Device`] will use.
377    pub fn set_event_index(&self, index: u32) -> Result<()> {
378        let request = SetEventIndexRequest::new(self.id, index, 0x55aaaa55);
379        let response: SetEventIndexResponse = send_and_receive(request, self)?;
380        if response.success {
381            Ok(())
382        } else {
383            bail!("SetEventIndex failed")
384        }
385    }
386
387    /// Set the listener (IP:PORT) the [`Device`] will use to send [`Status`] messages to over UDP.
388    pub fn set_listener(&self, address: Ipv4Addr, port: u16) -> Result<()> {
389        let request = SetListenerRequest::new(self.id, address, port);
390        let response: SetListenerResponse = send_and_receive(request, self)?;
391        if response.success {
392            Ok(())
393        } else {
394            bail!("SetListener failed")
395        }
396    }
397
398    /// Set IP address, subnet mask and gateway for the [`Device`].
399    pub fn set_network_config(
400        &self,
401        address: Ipv4Addr,
402        subnet: Ipv4Addr,
403        gateway: Ipv4Addr,
404    ) -> Result<()> {
405        let request = SetAddressRequest::new(self.id, address, subnet, gateway, 0x55aaaa55);
406
407        send(request, self)
408    }
409
410    /// Enable the recording of special events.
411    pub fn enable_record_special_events(&self, enable: bool) -> Result<()> {
412        let request = SetRecordSpecialEventsRequest::new(self.id, enable);
413        let response: SetRecordSpecialEventsResponse = send_and_receive(request, self)?;
414        if response.success {
415            Ok(())
416        } else {
417            bail!("SetRecordSpecialEvents failed")
418        }
419    }
420
421    /// Set the local time of the [`Device`].
422    pub fn set_time(&self, datetime: NaiveDateTime) -> Result<NaiveDateTime> {
423        let request = SetTimeRequest::new(self.id, datetime.try_into()?);
424        let response: SetTimeResponse = send_and_receive(request, self)?;
425        response.datetime.try_into()
426    }
427
428    /// Add or update new [`TimeProfile`] to the [`Device`].
429    pub fn add_or_update_time_profile(&self, profile: TimeProfile) -> Result<()> {
430        let request = SetTimeProfileRequest::new(
431            self.id,
432            profile.id,
433            profile.from.try_into()?,
434            profile.to.try_into()?,
435            profile.monday,
436            profile.tuesday,
437            profile.wednesday,
438            profile.thursday,
439            profile.friday,
440            profile.saturday,
441            profile.sunday,
442            profile.segments[0].start.try_into()?,
443            profile.segments[0].end.try_into()?,
444            profile.segments[1].start.try_into()?,
445            profile.segments[1].end.try_into()?,
446            profile.segments[2].start.try_into()?,
447            profile.segments[2].end.try_into()?,
448            profile.linked_profile_id,
449        );
450        let response: SetTimeProfileResponse = send_and_receive(request, self)?;
451        if response.success {
452            Ok(())
453        } else {
454            bail!("SetTimeProfile failed")
455        }
456    }
457}
458
459/// Send a [`Request`] and receive a [`Response`].
460fn send_and_receive<T: messages::Request, S: messages::Response + Debug>(
461    request: T,
462    d: &Device,
463) -> Result<S> {
464    let socket = setup_socket(d.u)?;
465    let addr = get_address(d);
466    socket.send_to(
467        &request.to_bytes(),
468        &SocketAddr::new(addr.into(), UHPPOTE_PORT),
469    )?;
470
471    // Receive the response
472    let mut buf = [0u8; 64];
473    socket.recv(&mut buf)?;
474
475    S::from_bytes(&buf)
476}
477
478/// Send a [`Request`] to the [`Device`], but don't expect a response.
479fn send<T: messages::Request>(request: T, d: &Device) -> Result<()> {
480    let socket = setup_socket(d.u)?;
481    let addr = get_address(d);
482    socket.send_to(
483        &request.to_bytes(),
484        &SocketAddr::new(addr.into(), UHPPOTE_PORT),
485    )?;
486    Ok(())
487}
488
489/// Get the IP address of the [`Device`]. If None, use the broadcast address from [`Uhppoted`]
490fn get_address(d: &Device) -> Ipv4Addr {
491    match d.ip_address {
492        Some(ip) => ip,
493        None => d.u.broadcast_address,
494    }
495}
496
497/// Setup a socket with correct timeouts.
498fn setup_socket(u: &Uhppoted) -> Result<UdpSocket, anyhow::Error> {
499    let socket = UdpSocket::bind(u.bind_address)?;
500    socket.set_write_timeout(Some(Duration::new(1, 0)))?;
501    socket.set_read_timeout(Some(u.timeout))?;
502    socket.set_broadcast(true)?;
503    Ok(socket)
504}
505
506/// Broadcast a [`Request`] to all [`Device`]s.
507fn broadcast_and_receive<T: messages::Request, S: messages::Response + Debug>(
508    request: T,
509    u: &Uhppoted,
510) -> Result<Vec<S>> {
511    let socket = setup_socket(u)?;
512
513    let to_addr = SocketAddr::new(u.broadcast_address.into(), UHPPOTE_PORT);
514
515    socket.send_to(&request.to_bytes(), to_addr)?;
516    let mut buf = [0u8; 64];
517
518    let mut ret = Vec::new();
519
520    while let Ok((_, _)) = socket.recv_from(&mut buf) {
521        ret.push(S::from_bytes(&buf)?);
522    }
523
524    Ok(ret)
525}