1use crate::constants::{KEY_CODES, OK_CODES};
2use crate::dvrip::DVRIPCam;
3use crate::error::Result;
4use async_trait::async_trait;
5use serde_json::json;
6use strum_macros::AsRefStr;
7use tokio::time::{Duration, sleep};
8
9#[derive(Debug, Clone, Copy, AsRefStr)]
10pub enum PTZCommand {
11 DirectionUp,
12 DirectionDown,
13 DirectionLeft,
14 DirectionRight,
15 DirectionLeftUp,
16 DirectionLeftDown,
17 DirectionRightUp,
18 DirectionRightDown,
19 ZoomTile,
20 ZoomWide,
21 FocusNear,
22 FocusFar,
23 IrisSmall,
24 IrisLarge,
25 SetPreset,
26 GotoPreset,
27 ClearPreset,
28 StartTour,
29 StopTour,
30}
31
32#[async_trait]
33pub trait PTZ: Send + Sync {
34 async fn ptz(&mut self, cmd: PTZCommand, step: u8, preset: i32, channel: u8) -> Result<bool>;
36
37 async fn ptz_step(&mut self, cmd: PTZCommand, step: u8) -> Result<bool>;
39
40 async fn key_down(&mut self, key: &str) -> Result<bool>;
42
43 async fn key_up(&mut self, key: &str) -> Result<bool>;
45
46 async fn key_press(&mut self, key: &str) -> Result<bool>;
48
49 async fn key_script(&mut self, keys: &str) -> Result<bool>;
51}
52
53#[async_trait]
54impl PTZ for DVRIPCam {
55 async fn ptz(&mut self, cmd: PTZCommand, step: u8, preset: i32, channel: u8) -> Result<bool> {
56 let cmd_str = cmd.as_ref().to_string();
57 let ptz_param = json!({
58 "AUX": {"Number": 0, "Status": "On"},
59 "Channel": channel,
60 "MenuOpts": "Enter",
61 "Pattern": "Start",
62 "Preset": preset,
63 "Step": step,
64 "Tour": if cmd_str.contains("Tour") { 1 } else { 0 },
65 });
66
67 let data = json!({
68 "Command": cmd_str,
69 "Parameter": ptz_param,
70 });
71
72 let reply = self.set_command("OPPTZControl", data, None).await?;
73 if let Some(ret) = reply.get("Ret").and_then(|r| r.as_u64()) {
74 return Ok(OK_CODES.contains(&(ret as u32)));
75 }
76 Ok(false)
77 }
78
79 async fn ptz_step(&mut self, cmd: PTZCommand, step: u8) -> Result<bool> {
80 let cmd_str = cmd.as_ref().to_string();
81
82 let params_start = json!({
84 "AUX": {"Number": 0, "Status": "On"},
85 "Channel": 0,
86 "MenuOpts": "Enter",
87 "POINT": {"bottom": 0, "left": 0, "right": 0, "top": 0},
88 "Pattern": "SetBegin",
89 "Preset": 65535,
90 "Step": step,
91 "Tour": 0,
92 });
93
94 let data_start = json!({
95 "Command": cmd_str,
96 "Parameter": params_start,
97 });
98
99 self.set_command("OPPTZControl", data_start, None).await?;
100
101 let params_end = json!({
103 "AUX": {"Number": 0, "Status": "On"},
104 "Channel": 0,
105 "MenuOpts": "Enter",
106 "POINT": {"bottom": 0, "left": 0, "right": 0, "top": 0},
107 "Pattern": "SetBegin",
108 "Preset": -1,
109 "Step": step,
110 "Tour": 0,
111 });
112
113 let data_end = json!({
114 "Command": cmd_str,
115 "Parameter": params_end,
116 });
117
118 let reply = self.set_command("OPPTZControl", data_end, None).await?;
119 if let Some(ret) = reply.get("Ret").and_then(|r| r.as_u64()) {
120 return Ok(OK_CODES.contains(&(ret as u32)));
121 }
122 Ok(false)
123 }
124
125 async fn key_down(&mut self, key: &str) -> Result<bool> {
126 let data = json!({
127 "Status": "KeyDown",
128 "Value": key,
129 });
130
131 let reply = self.set_command("OPNetKeyboard", data, None).await?;
132 if let Some(ret) = reply.get("Ret").and_then(|r| r.as_u64()) {
133 return Ok(OK_CODES.contains(&(ret as u32)));
134 }
135 Ok(false)
136 }
137
138 async fn key_up(&mut self, key: &str) -> Result<bool> {
139 let data = json!({
140 "Status": "KeyUp",
141 "Value": key,
142 });
143
144 let reply = self.set_command("OPNetKeyboard", data, None).await?;
145 if let Some(ret) = reply.get("Ret").and_then(|r| r.as_u64()) {
146 return Ok(OK_CODES.contains(&(ret as u32)));
147 }
148 Ok(false)
149 }
150
151 async fn key_press(&mut self, key: &str) -> Result<bool> {
152 self.key_down(key).await?;
153 sleep(Duration::from_millis(300)).await;
154 self.key_up(key).await
155 }
156
157 async fn key_script(&mut self, keys: &str) -> Result<bool> {
158 for k in keys.chars() {
159 if k != ' ' {
160 let key_upper = k.to_uppercase().to_string();
161 if let Some(key_code) = KEY_CODES.get(key_upper.as_str()) {
162 self.key_press(key_code).await?;
163 }
164 } else {
165 sleep(Duration::from_secs(1)).await;
166 }
167 }
168 Ok(true)
169 }
170}