1#![doc(html_root_url = "https://docs.rs/uhppote-rs/1/")]
2mod 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 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 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 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 pub fn get_device(&self, id: u32, ip_address: Option<Ipv4Addr>) -> Device {
93 Device::new(self, id, ip_address)
94 }
95
96 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 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 fn new(u: &'a Uhppoted, id: u32, ip_address: Option<Ipv4Addr>) -> Device<'a> {
155 Device { u, id, ip_address }
156 }
157
158 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
459fn 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 let mut buf = [0u8; 64];
473 socket.recv(&mut buf)?;
474
475 S::from_bytes(&buf)
476}
477
478fn 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
489fn get_address(d: &Device) -> Ipv4Addr {
491 match d.ip_address {
492 Some(ip) => ip,
493 None => d.u.broadcast_address,
494 }
495}
496
497fn 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
506fn 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}