Skip to main content

dvrip_rs/commands/
ptz.rs

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    /// Control PTZ with continuous command
35    async fn ptz(&mut self, cmd: PTZCommand, step: u8, preset: i32, channel: u8) -> Result<bool>;
36
37    /// Control PTZ with single step movement
38    async fn ptz_step(&mut self, cmd: PTZCommand, step: u8) -> Result<bool>;
39
40    /// Press a key (keyDown)
41    async fn key_down(&mut self, key: &str) -> Result<bool>;
42
43    /// Release a key (keyUp)
44    async fn key_up(&mut self, key: &str) -> Result<bool>;
45
46    /// Press and release a key
47    async fn key_press(&mut self, key: &str) -> Result<bool>;
48
49    /// Execute a key script
50    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        // Start Movement
83        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        // Stop movement
102        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}