Skip to content

Commit

Permalink
Merge branch 'motion'
Browse files Browse the repository at this point in the history
  • Loading branch information
thirtythreeforty committed Jan 9, 2023
2 parents f73527b + 6848a79 commit 7087b34
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 46 deletions.
4 changes: 4 additions & 0 deletions crates/core/src/bc/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub const MSG_ID_TALKABILITY: u32 = 10;
pub const MSG_ID_TALKRESET: u32 = 11;
/// Reboot messages have this ID
pub const MSG_ID_REBOOT: u32 = 23;
/// Request motion detection messages
pub const MSG_ID_MOTION_REQUEST: u32 = 31;
/// Motion detection messages
pub const MSG_ID_MOTION: u32 = 33;
/// Version messages have this ID
pub const MSG_ID_VERSION: u32 = 80;
/// Getting PIR status messages have this ID
Expand Down
32 changes: 32 additions & 0 deletions crates/core/src/bc/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ pub struct BcXml {
/// Revieced as part of the TalkAbility request
#[yaserde(rename = "TalkAbility")]
pub talk_ability: Option<TalkAbility>,
/// Received when motion is detected
#[yaserde(rename = "AlarmEventList")]
pub alarm_event_list: Option<AlarmEventList>,
}

impl BcXml {
Expand Down Expand Up @@ -412,6 +415,35 @@ pub struct AudioConfigList {
pub audio_config: AudioConfig,
}

/// An XML that desctibes a list of events such as motion detection
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
pub struct AlarmEventList {
/// XML Version
#[yaserde(attribute)]
pub version: String,
/// List of events
#[yaserde(rename = "AlarmEvent")]
pub alarm_events: Vec<AlarmEvent>,
}

/// An alarm event. Camera can send multiple per message as an array in AlarmEventList.
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
pub struct AlarmEvent {
/// XML Version
#[yaserde(attribute)]
pub version: String,
/// The channel the event occured on. Usually zero unless from an NVR
#[yaserde(rename = "channelId")]
pub channel_id: u8,
/// Motion status. Known values are `"MD"` or `"none"`
pub status: String,
/// The recording status. Known values `0` or `1`
pub recording: i32,
/// The timestamp associated with the recording. `0` if not recording
#[yaserde(rename = "timeStamp")]
pub timeStamp: i32,
}

/// Convience function to return the xml version used throughout the library
pub fn xml_ver() -> String {
"1.1".to_string()
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/bc_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod errors;
mod ledstate;
mod login;
mod logout;
mod motion;
mod ping;
mod pirstate;
mod reboot;
Expand All @@ -27,6 +28,7 @@ pub use errors::Error;
pub use ledstate::LightState;
pub use pirstate::PirState;
pub use resolution::*;
pub use motion::{MotionOutput, MotionOutputError, MotionStatus};
pub use stream::{Stream, StreamOutput, StreamOutputError};

type Result<T> = std::result::Result<T, Error>;
Expand Down
5 changes: 1 addition & 4 deletions crates/core/src/bc_protocol/connection/filesub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ pub struct FileSubscriber {
}

impl FileSubscriber {
/// Creates a binary subsciber from a BcSubscrption.
/// When reading the next packet it will skip over multiple
/// Bc packets to fill the binary buffer so ensure you
/// only want binary packets when calling read
/// Creates a file subsciber from a list of files
pub fn from_files<P: AsRef<Path>>(paths: Vec<P>) -> FileSubscriber {
FileSubscriber {
files: paths
Expand Down
8 changes: 4 additions & 4 deletions crates/core/src/bc_protocol/ledstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::bc::{model::*, xml::*};

impl BcCamera {
/// Get the [LedState] xml which contains the LED status of the camera
pub fn get_ledstate(&mut self) -> Result<LedState> {
pub fn get_ledstate(&self) -> Result<LedState> {
let connection = self
.connection
.as_ref()
Expand Down Expand Up @@ -49,7 +49,7 @@ impl BcCamera {
}

/// Set the led lights using the [LedState] xml
pub fn set_ledstate(&mut self, mut led_state: LedState) -> Result<()> {
pub fn set_ledstate(&self, mut led_state: LedState) -> Result<()> {
let connection = self
.connection
.as_ref()
Expand Down Expand Up @@ -100,7 +100,7 @@ impl BcCamera {
///
/// This is for the RED IR lights that can come on automaitcally
/// during low light.
pub fn irled_light_set(&mut self, state: LightState) -> Result<()> {
pub fn irled_light_set(&self, state: LightState) -> Result<()> {
let mut led_state = self.get_ledstate()?;
led_state.state = match state {
LightState::On => "open".to_string(),
Expand All @@ -115,7 +115,7 @@ impl BcCamera {
/// True is on and false is off
///
/// This is for the little blue on light of some camera
pub fn led_light_set(&mut self, state: bool) -> Result<()> {
pub fn led_light_set(&self, state: bool) -> Result<()> {
let mut led_state = self.get_ledstate()?;
led_state.light_state = match state {
true => "open".to_string(),
Expand Down
132 changes: 132 additions & 0 deletions crates/core/src/bc_protocol/motion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use super::{BcCamera, Error, Result, RX_TIMEOUT};
use crate::bc::{model::*, xml::*};

/// Motion Status that the callback can send
pub enum MotionStatus {
/// Sent when motion is first detected
Start,
/// Sent when motion stops
Stop,
/// Sent when an Alarm about something other than motion was received
NoChange,
}

/// This is a conveince type for the error of the MotionOutput callback
pub type MotionOutputError = Result<bool>;

/// Trait used as part of [`listen_on_motion`] to send motion messages
pub trait MotionOutput {
/// This is the callback used when motion is received
///
/// If result is `Ok(true)` more messages will be sent
///
/// If result if `Ok(false)` then message will be stopped
///
/// If result is `Err(E)` then motion messages be stopped
/// and an error will be thrown
fn motion_recv(&mut self, motion_status: MotionStatus) -> MotionOutputError;
}

impl BcCamera {
/// This message tells the camera to send the motion events to us
/// Which are the recieved on msgid 33
fn start_motion_query(&self) -> Result<()> {
let connection = self
.connection
.as_ref()
.expect("Must be connected to listen to messages");

let sub = connection.subscribe(MSG_ID_MOTION_REQUEST)?;
let msg = Bc {
meta: BcMeta {
msg_id: MSG_ID_MOTION_REQUEST,
channel_id: self.channel_id,
msg_num: self.new_message_num(),
stream_type: 0,
response_code: 0,
class: 0x6414,
},
body: BcBody::ModernMsg(ModernMsg {
..Default::default()
}),
};

sub.send(msg)?;

let msg = sub.rx.recv_timeout(RX_TIMEOUT)?;

if let BcMeta {
response_code: 200, ..
} = msg.meta
{
Ok(())
} else {
Err(Error::UnintelligibleReply {
reply: msg,
why: "The camera did not accept the request to start motion",
})
}
}

/// This requests that motion messages be listen to and sent to the
/// output struct.
///
/// The output structure must implement the [`MotionCallback`] trait
pub fn listen_on_motion<T: MotionOutput>(&self, data_out: &mut T) -> Result<()> {
self.start_motion_query()?;

let connection = self
.connection
.as_ref()
.expect("Must be connected to listen to messages");

// After start_motion_query (MSG_ID 31) the camera sends motion messages
// when whenever motion is detected.
let sub = connection.subscribe(MSG_ID_MOTION)?;

loop {
// Mostly ignore when timout is reached because these messages are only
// sent when motion is detected which might means hours between messages
// being received
let msg = sub.rx.recv_timeout(RX_TIMEOUT);
let status = match msg {
Ok(motion_msg) => {
if let BcBody::ModernMsg(ModernMsg {
payload:
Some(BcPayloads::BcXml(BcXml {
alarm_event_list: Some(alarm_event_list),
..
})),
..
}) = motion_msg.body
{
let mut result = MotionStatus::NoChange;
for alarm_event in &alarm_event_list.alarm_events {
if alarm_event.channel_id == self.channel_id {
if alarm_event.status == "MD" {
result = MotionStatus::Start;
break;
} else if alarm_event.status == "none" {
result = MotionStatus::Stop;
break;
}
}
}
Ok(result)
} else {
Ok(MotionStatus::NoChange)
}
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => Ok(MotionStatus::NoChange),
// On connection drop we stop
Err(e @ std::sync::mpsc::RecvTimeoutError::Disconnected) => Err(e),
}?;

match data_out.motion_recv(status) {
Ok(true) => {}
Ok(false) => return Ok(()),
Err(e) => return Err(e),
}
}
}
}
2 changes: 1 addition & 1 deletion crates/core/src/bc_protocol/reboot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl BcCamera {
} else {
Err(Error::UnintelligibleReply {
reply: msg,
why: "The camera did not except the TalkConfig xml",
why: "The camera did not accept the reboot command",
})
}
}
Expand Down
20 changes: 15 additions & 5 deletions crates/core/src/bc_protocol/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ use super::{BcCamera, BinarySubscriber, Result};
use crate::{
bc::{model::*, xml::*},
bcmedia::model::*,
Never,
};

/// Convience type for the error raised by the [StreamOutput] trait
pub type StreamOutputError = Result<()>;
pub type StreamOutputError = Result<bool>;

/// The method [`BcCamera::start_video()`] requires a structure with this trait to pass the
/// audio and video data back to
pub trait StreamOutput {
/// This is the callback raised a complete media packet is received
fn write(&mut self, media: BcMedia) -> StreamOutputError;
///
/// If result is `Ok(true)` more messages will be sent
///
/// If result if `Ok(false)` then message will be stopped
///
/// If result is `Err(E)` then messages be stopped
/// and an error will be thrown
fn stream_recv(&mut self, media: BcMedia) -> StreamOutputError;
}

/// The stream names supported by BC
Expand Down Expand Up @@ -46,7 +52,7 @@ impl BcCamera {
///
/// This will block forever or return an error when the camera connection is dropped
///
pub fn start_video<Outputs>(&self, data_outs: &mut Outputs, stream: Stream) -> Result<Never>
pub fn start_video<Outputs>(&self, data_outs: &mut Outputs, stream: Stream) -> Result<()>
where
Outputs: StreamOutput,
{
Expand Down Expand Up @@ -119,7 +125,11 @@ impl BcCamera {
loop {
let bc_media = BcMedia::deserialize(&mut media_sub)?;
// We now have a complete interesting packet. Send it to on the callback
data_outs.write(bc_media)?;
match data_outs.stream_recv(bc_media) {
Ok(true) => {}
Ok(false) => return Ok(()),
Err(e) => return Err(e),
};
}
}
}
5 changes: 0 additions & 5 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ pub mod bcmedia;
/// Contains low level structures and formats for the udpstream
pub mod bcudp;

#[derive(Debug)]
/// Certain method just as `start_video` will block forever or return an error
/// In such a case the return type is `Result<Never, Error>`
pub enum Never {}

/// This is the top level error structure of the library
///
/// Most commands will either return their `Ok(result)` or this `Err(Error)`
Expand Down
4 changes: 2 additions & 2 deletions src/rtsp/gst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ enum StreamFormat {
}

impl StreamOutput for GstOutputs {
fn write(&mut self, media: BcMedia) -> StreamOutputError {
fn stream_recv(&mut self, media: BcMedia) -> StreamOutputError {
match media {
BcMedia::Iframe(payload) => {
let video_type = match payload.video_type {
Expand Down Expand Up @@ -88,7 +88,7 @@ impl StreamOutput for GstOutputs {
}
}

Ok(())
Ok(true)
}
}

Expand Down
Loading

0 comments on commit 7087b34

Please sign in to comment.