Skip to content

Commit

Permalink
Merge pull request thirtythreeforty#92 from danielkaiser/feature-ptz-…
Browse files Browse the repository at this point in the history
…preset
  • Loading branch information
QuantumEntangledAndy authored May 29, 2023
2 parents fdff244 + 4628946 commit d961bf3
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ Control messages:
- `/control/reboot` Reboot the camera
- `/control/ptz [up|down|left|right|in|out]` (amount) Control the PTZ
movements, amount defaults to 32.0
- `/control/preset [id]` Move to PTZ preset `id`
- `/control/pir [on|off]`

Status Messages:
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/bc/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub const MSG_ID_TALKABILITY: u32 = 10;
pub const MSG_ID_TALKRESET: u32 = 11;
/// PtzControl messages have this ID
pub const MSG_ID_PTZ_CONTROL: u32 = 18;
/// PTZ goto preset position
pub const MSG_ID_PTZ_CONTROL_PRESET: u32 = 19;
/// Reboot messages have this ID
pub const MSG_ID_REBOOT: u32 = 23;
/// Request motion detection messages
Expand All @@ -35,6 +37,8 @@ pub const MSG_ID_GET_GENERAL: u32 = 104;
pub const MSG_ID_ABILITY_INFO: u32 = 151;
/// Setting general system info (clock mostly) messages have this ID
pub const MSG_ID_SET_GENERAL: u32 = 105;
/// Get the available PTZ position presets
pub const MSG_ID_GET_PTZ_PRESET: u32 = 190;
/// Will send the talk config for talk back data to follow this msg
pub const MSG_ID_TALKCONFIG: u32 = 201;
/// Used to send talk back binary data
Expand Down
38 changes: 37 additions & 1 deletion crates/core/src/bc/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ pub struct BcXml {
/// Received when the floodlight status is updated
#[yaserde(rename = "FloodlightStatusList")]
pub floodlight_status_list: Option<FloodlightStatusList>,
/// Sent or received for the PTZ preset functionality
#[yaserde(rename = "PtzPreset")]
pub ptz_preset: Option<PtzPreset>,
/// Recieved on login/low battery events
#[yaserde(rename = "BatteryList")]
pub battery_list: Option<BatteryList>,
Expand Down Expand Up @@ -564,10 +567,43 @@ pub struct PtzControl {
pub channel_id: u8,
/// The amount of movement to perform
pub speed: f32,
/// The direction to transverse. Known directions: "right"
/// The direction to transverse. Known values are `"left"`, `"right"`, `"up"`, `"down"`,
/// `"leftUp"`, `"leftDown"`, `"rightUp"`, `"rightDown"` and `"stop"`
pub command: String,
}

/// An XML that describes a list of available PTZ presets
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
pub struct PtzPreset {
/// XML Version
#[yaserde(attribute)]
pub version: String,
/// The channel ID. Usually zero unless from an NVR
#[yaserde(rename = "channelId")]
pub channel_id: u8,
/// List of presets
#[yaserde(rename = "presetList")]
pub preset_list: Option<PresetList>,
}

/// A preset list
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
pub struct PresetList {
/// List of Presets
pub preset: Vec<Preset>,
}

/// A preset. Either contains the ID and the name or the ID and the command
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
pub struct Preset {
/// The ID of the preset
pub id: i8,
/// The preset name
pub name: Option<String>,
/// Command: Known values: `"toPos"` and `"setPos"`
pub command: Option<String>,
}

/// A list of battery infos. This message is sent from the camera as
/// an event
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
Expand Down
105 changes: 105 additions & 0 deletions crates/core/src/bc_protocol/ptz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,109 @@ impl BcCamera {
})
}
}

/// Get the [PtzPreset] XML which contains the list of the preset positions known to the camera
pub async fn get_ptz_preset(&self) -> Result<PtzPreset> {
self.has_ability_rw("control").await?;
let connection = self.get_connection();
let msg_num = self.new_message_num();
let mut sub_set = connection.subscribe(msg_num).await?;

let send = Bc {
meta: BcMeta {
msg_id: MSG_ID_GET_PTZ_PRESET,
channel_id: self.channel_id,
msg_num,
response_code: 0,
stream_type: 0,
class: 0x6414,
},
body: BcBody::ModernMsg(ModernMsg {
extension: Some(Extension {
channel_id: Some(self.channel_id),
..Default::default()
}),
payload: None,
}),
};

sub_set.send(send).await?;
let msg = sub_set.recv().await?;

if let BcBody::ModernMsg(ModernMsg {
payload:
Some(BcPayloads::BcXml(BcXml {
ptz_preset: Some(ptz_preset),
..
})),
..
}) = msg.body
{
Ok(ptz_preset)
} else {
Err(Error::UnintelligibleReply {
reply: std::sync::Arc::new(Box::new(msg)),
why: "The camera did not return a valid PtzPreset xml",
})
}
}

/// Set a PTZ preset. If a [name] is given the current position will be saved as a preset
/// with the given [preset_id] and [name], otherwise the camera will attempt to move to the
/// preset with the given ID.
pub async fn set_ptz_preset(&self, preset_id: i8, name: Option<String>) -> Result<()> {
self.has_ability_rw("control").await?;
let connection = self.get_connection();
let msg_num = self.new_message_num();
let mut sub_set = connection.subscribe(msg_num).await?;

let command = if name.is_some() { "setPos" } else { "toPos" };
let preset = Preset {
id: preset_id,
name,
command: Some(command.to_string()),
..Default::default()
};
let send = Bc {
meta: BcMeta {
msg_id: MSG_ID_PTZ_CONTROL_PRESET,
channel_id: self.channel_id,
msg_num,
response_code: 0,
stream_type: 0,
class: 0x6414,
},

body: BcBody::ModernMsg(ModernMsg {
extension: Some(Extension {
channel_id: Some(self.channel_id),
..Default::default()
}),
payload: Some(BcPayloads::BcXml(BcXml {
ptz_preset: Some(PtzPreset {
preset_list: Some(PresetList {
preset: vec![preset],
}),
..Default::default()
}),
..Default::default()
})),
}),
};

sub_set.send(send).await?;
let msg = sub_set.recv().await?;

if let BcMeta {
response_code: 200, ..
} = msg.meta
{
Ok(())
} else {
Err(Error::UnintelligibleReply {
reply: std::sync::Arc::new(Box::new(msg)),
why: "The camera did not accept the PtzPreset xml",
})
}
}
}
1 change: 1 addition & 0 deletions dissector/baichuan.lua
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ local message_types = {
[15]="<FileInfoList>",
[16]="<FileInfoList>",
[18]="<PtzControl>",
[19]="<PtzPreset>",
[23]="Reboot",
[25]="<VideoInput> (write)",
[26]="<VideoInput>", -- <InputAdvanceCfg>
Expand Down
50 changes: 49 additions & 1 deletion dissector/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,10 @@ Message have zero to two payloads.
</PtzControl>
</body>
```

- **Notes** : The known movement commands are `"left"`, `"right"`, `"up"`, `"down"`, `"leftUp"`,
`"leftDown"`, `"rightUp"`, `"rightDown"` and `"stop"` although the diagonal movement
does not seem to work.

- Camera

Expand All @@ -363,6 +367,45 @@ Message have zero to two payloads.
|--------------|--------------|----------------|-------------------|-------------------|---------------|---------------|
| 0a bc de f0 | 00 00 00 12 | 00 00 00 00 | 1e 00 00 00 | c8 00 | 00 00 | 00 00 00 00 |

- 19: `<PtzPreset>`

- Client

- Header

| Magic | Message ID | Message Length | Encryption Offset | Status Code | Message Class | Payload Offset |
|--------------|-------------|----------------|-------------------|-------------------|---------------|---------------|
| 0a bc de f0 | 00 00 00 13 | 00 00 01 44 | 1e 00 00 00 | 00 00 | 64 14 | 00 00 00 00 |

- Payload

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<body>
<PtzPreset version="1.1">
<channelId>0</channelId>
<presetList>
<preset>
<id>0</id>
<command>setPos</command>
<name>Test</name>
</preset>
</presetList>
</PtzPreset>
</body>
```

- **Notes** : The known values for command are `"setPos"` and `"toPos"`

- Camera

- Header

| Magic | Message ID | Message Length | Encryption Offset | Status Code | Message Class | Payload Offset |
|--------------|-------------|----------------|-------------------|-------------------|---------------|---------------|
| 0a bc de f0 | 00 00 00 13 | 00 00 00 00 | 1e 00 00 00 | c8 00 | 00 00 | 00 00 00 00 |


- 23: `Reboot`

- Client
Expand Down Expand Up @@ -1987,7 +2030,12 @@ Message have zero to two payloads.
<body>
<PtzPreset version="1.1">
<channelId>0</channelId>
<presetList />
<presetList>
<preset>
<id>0</id>
<name>Test</name>
</preset>
</presetList>
</PtzPreset>
</body>
```
Expand Down
1 change: 1 addition & 0 deletions src/cmdline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum Command {
StatusLight(super::statusled::Opt),
Reboot(super::reboot::Opt),
Pir(super::pir::Opt),
Ptz(super::ptz::Opt),
Talk(super::talk::Opt),
Mqtt(super::mqtt::Opt),
Image(super::image::Opt),
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mod config;
mod image;
mod mqtt;
mod pir;
mod ptz;
mod reboot;
mod rtsp;
mod statusled;
Expand Down Expand Up @@ -102,6 +103,9 @@ async fn main() -> Result<()> {
Some(Command::Pir(opts)) => {
pir::main(opts, config).await?;
}
Some(Command::Ptz(opts)) => {
ptz::main(opts, config).await?;
}
Some(Command::Talk(opts)) => {
talk::main(opts, config).await?;
}
Expand Down
41 changes: 22 additions & 19 deletions src/mqtt/event_cam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub(crate) enum Messages {
PIROff,
PIRQuery,
Ptz(Direction),
Preset(i8),
}

#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -510,44 +511,46 @@ impl<'a> MessageHandler<'a> {
}
}
Messages::Ptz(direction) => {
let (bc_direction, amount, seconds) = match direction {
Direction::Up(amount, seconds) => {
(BcDirection::Up, amount, seconds)
let (bc_direction, speed, seconds) = match direction {
Direction::Up(speed, seconds) => (BcDirection::Up, speed, seconds),
Direction::Down(speed, seconds) => {
(BcDirection::Down, speed, seconds)
}
Direction::Down(amount, seconds) => {
(BcDirection::Down, amount, seconds)
Direction::Left(speed, seconds) => {
(BcDirection::Left, speed, seconds)
}
Direction::Left(amount, seconds) => {
(BcDirection::Left, amount, seconds)
Direction::Right(speed, seconds) => {
(BcDirection::Right, speed, seconds)
}
Direction::Right(amount, seconds) => {
(BcDirection::Right, amount, seconds)
}
Direction::In(amount, seconds) => {
(BcDirection::In, amount, seconds)
}
Direction::Out(amount, seconds) => {
(BcDirection::Out, amount, seconds)
Direction::In(speed, seconds) => (BcDirection::In, speed, seconds),
Direction::Out(speed, seconds) => {
(BcDirection::Out, speed, seconds)
}
};
if let Err(e) = self.camera.send_ptz(bc_direction, amount).await {
if let Err(e) = self.camera.send_ptz(bc_direction, speed).await {
error = Some(format!("Failed to send PTZ: {:?}", e));
"FAIL".to_string()
} else {
// sleep for the designated seconds
sleep(Duration::from_secs_f32(seconds)).await;

// note that amount is not used in the stop command
if let Err(e) =
self.camera.send_ptz(BcDirection::Stop, amount).await
{
if let Err(e) = self.camera.send_ptz(BcDirection::Stop, 0.0).await {
error = Some(format!("Failed to send PTZ: {:?}", e));
"FAIL".to_string()
} else {
"OK".to_string()
}
}
}
Messages::Preset(id) => {
if let Err(e) = self.camera.set_ptz_preset(id, None).await {
error = Some(format!("Failed to send PTZ preset: {:?}", e));
"FAIL".to_string()
} else {
"OK".to_string()
}
}
_ => "UNKNOWN COMMAND".to_string(),
};
if let Some(replier) = replier {
Expand Down
Loading

0 comments on commit d961bf3

Please sign in to comment.