diff --git a/crates/core/src/bc/model.rs b/crates/core/src/bc/model.rs index f416677a..8ddbd87e 100644 --- a/crates/core/src/bc/model.rs +++ b/crates/core/src/bc/model.rs @@ -31,6 +31,10 @@ pub const MSG_ID_REBOOT: u32 = 23; pub const MSG_ID_MOTION_REQUEST: u32 = 31; /// Motion detection messages pub const MSG_ID_MOTION: u32 = 33; +/// Set service ports +pub const MSG_ID_SET_SERVICE_PORTS: u32 = 36; +/// Get service ports +pub const MSG_ID_GET_SERVICE_PORTS: u32 = 37; /// Version messages have this ID pub const MSG_ID_VERSION: u32 = 80; /// Ping messages have this ID diff --git a/crates/core/src/bc/xml.rs b/crates/core/src/bc/xml.rs index b0eaaaf7..c5db8214 100644 --- a/crates/core/src/bc/xml.rs +++ b/crates/core/src/bc/xml.rs @@ -114,6 +114,24 @@ pub struct BcXml { /// Play a sound #[serde(rename = "audioPlayInfo", skip_serializing_if = "Option::is_none")] pub audio_play_info: Option, + /// For changing baichaun server port + #[serde(rename = "ServerPort", skip_serializing_if = "Option::is_none")] + pub server_port: Option, + /// For changing http server port + #[serde(rename = "HttpPort", skip_serializing_if = "Option::is_none")] + pub http_port: Option, + /// For changing https server port + #[serde(rename = "HttpsPort", skip_serializing_if = "Option::is_none")] + pub https_port: Option, + /// For changing rtsp server port + #[serde(rename = "RtspPort", skip_serializing_if = "Option::is_none")] + pub rtsp_port: Option, + /// For changing rtmp server port + #[serde(rename = "RtmpPort", skip_serializing_if = "Option::is_none")] + pub rtmp_port: Option, + /// For changing rtmp server port + #[serde(rename = "OnvifPort", skip_serializing_if = "Option::is_none")] + pub onvif_port: Option, } impl BcXml { @@ -1373,6 +1391,90 @@ pub struct AudioPlayInfo { pub on_off: u32, } +/// Server port for baichaun defaults 9000 +#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)] +pub struct ServerPort { + /// XML Version + #[serde(rename = "@version")] + pub version: String, + /// The port number + #[serde(rename = "serverPort")] + pub port: u32, + /// The enable status known values are `1`, `0` + #[serde(skip_serializing_if = "Option::is_none")] + pub enable: Option, +} + +/// Server port for http defaults to 80 +#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)] +pub struct HttpPort { + /// XML Version + #[serde(rename = "@version")] + pub version: String, + /// The port number + #[serde(rename = "httpPort")] + pub port: u32, + /// The enable status known values are `1`, `0` + #[serde(skip_serializing_if = "Option::is_none")] + pub enable: Option, +} + +/// Server port for https defaults to 443 +#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)] +pub struct HttpsPort { + /// XML Version + #[serde(rename = "@version")] + pub version: String, + /// The port number + #[serde(rename = "httpsPort")] + pub port: u32, + /// The enable status known values are `1`, `0` + #[serde(skip_serializing_if = "Option::is_none")] + pub enable: Option, +} + +/// Server port for Rtsp defaults to 554 +#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)] +pub struct RtspPort { + /// XML Version + #[serde(rename = "@version")] + pub version: String, + /// The port number + #[serde(rename = "rtspPort")] + pub port: u32, + /// The enable status known values are `1`, `0` + #[serde(skip_serializing_if = "Option::is_none")] + pub enable: Option, +} + +/// Server port for Rtmp defaults to 1935 +#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)] +pub struct RtmpPort { + /// XML Version + #[serde(rename = "@version")] + pub version: String, + /// The port number + #[serde(rename = "rtmpPort")] + pub port: u32, + /// The enable status known values are `1`, `0`, can be `None` on cameras that can't change it + #[serde(skip_serializing_if = "Option::is_none")] + pub enable: Option, +} + +/// Server port for Onvif defaults to 8000 +#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)] +pub struct OnvifPort { + /// XML Version + #[serde(rename = "@version")] + pub version: String, + /// The port number + #[serde(rename = "onvifPort")] + pub port: u32, + /// The enable status known values are `1`, `0` + #[serde(skip_serializing_if = "Option::is_none")] + pub enable: Option, +} + /// Convience function to return the xml version used throughout the library pub fn xml_ver() -> String { "1.1".to_string() diff --git a/crates/core/src/bc_protocol.rs b/crates/core/src/bc_protocol.rs index fd5447fb..681a2401 100644 --- a/crates/core/src/bc_protocol.rs +++ b/crates/core/src/bc_protocol.rs @@ -30,6 +30,7 @@ mod ptz; mod pushinfo; mod reboot; mod resolution; +mod services; mod siren; mod snap; mod stream; diff --git a/crates/core/src/bc_protocol/errors.rs b/crates/core/src/bc_protocol/errors.rs index dea34899..56e7323d 100644 --- a/crates/core/src/bc_protocol/errors.rs +++ b/crates/core/src/bc_protocol/errors.rs @@ -1,4 +1,4 @@ -use super::bc::model::Bc; +use super::bc::model::{Bc, BcXml}; use crate::NomErrorType; use thiserror::Error; @@ -35,6 +35,15 @@ pub enum Error { why: &'static str, }, + /// Raised when a BcXml reply was not understood + #[error("Communication error")] + UnintelligibleXml { + /// The Bc packet that was not understood + reply: std::sync::Arc>, + /// The message attached to the error + why: &'static str, + }, + /// Raised when the camera responds with a status code over than OK #[error("Camera responded with Service Unavaliable: {0}")] CameraServiceUnavailable(u16), diff --git a/crates/core/src/bc_protocol/services.rs b/crates/core/src/bc_protocol/services.rs new file mode 100644 index 00000000..8a70e4d4 --- /dev/null +++ b/crates/core/src/bc_protocol/services.rs @@ -0,0 +1,414 @@ +use super::{BcCamera, Error, Result}; +use crate::bc::{model::*, xml::*}; +use tokio::time::{interval, Duration}; + +impl BcCamera { + /// Helper to set the service state since they all share the same code + /// No checks are made to ensure the xml is valid service xml + /// hence private method + async fn set_services(&self, bcxml: BcXml) -> Result<()> { + let connection = self.get_connection(); + let msg_num = self.new_message_num(); + let mut sub_set = connection + .subscribe(MSG_ID_SET_SERVICE_PORTS, msg_num) + .await?; + + let get = Bc { + meta: BcMeta { + msg_id: MSG_ID_SET_SERVICE_PORTS, + channel_id: self.channel_id, + msg_num, + response_code: 0, + stream_type: 0, + class: 0x6414, + }, + body: BcBody::ModernMsg(ModernMsg { + extension: None, + payload: Some(BcPayloads::BcXml(bcxml)), + }), + }; + + sub_set.send(get).await?; + if let Ok(reply) = + tokio::time::timeout(tokio::time::Duration::from_micros(500), sub_set.recv()).await + { + let msg = reply?; + if msg.meta.response_code != 200 { + return Err(Error::CameraServiceUnavailable(msg.meta.response_code)); + } + + 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 except the BcXmp with service data", + }) + } + } else { + // Some cameras seem to just not send a reply on success, so after 500ms we return Ok + Ok(()) + } + } + + /// Helper since they all send the same message + /// No checks are made to ensure the xml is valid service xml + /// hence private method + async fn get_services(&self) -> Result { + let connection = self.get_connection(); + let mut reties: usize = 0; + let mut retry_interval = interval(Duration::from_millis(500)); + loop { + retry_interval.tick().await; + let msg_num = self.new_message_num(); + let mut sub_get = connection + .subscribe(MSG_ID_GET_SERVICE_PORTS, msg_num) + .await?; + let get = Bc { + meta: BcMeta { + msg_id: MSG_ID_GET_SERVICE_PORTS, + channel_id: self.channel_id, + msg_num, + response_code: 0, + stream_type: 0, + class: 0x6414, + }, + body: BcBody::ModernMsg(ModernMsg { + extension: None, + payload: None, + }), + }; + + sub_get.send(get).await?; + let msg = sub_get.recv().await?; + if msg.meta.response_code == 400 { + // Retryable + if reties < 5 { + reties += 1; + continue; + } else { + return Err(Error::CameraServiceUnavailable(msg.meta.response_code)); + } + } else if msg.meta.response_code != 200 { + return Err(Error::CameraServiceUnavailable(msg.meta.response_code)); + } else { + // Valid message with response_code == 200 + if let BcBody::ModernMsg(ModernMsg { + payload: Some(BcPayloads::BcXml(xml)), + .. + }) = msg.body + { + return Ok(xml); + } else { + return Err(Error::UnintelligibleReply { + reply: std::sync::Arc::new(Box::new(msg)), + why: "Expected ModernMsg payload but it was not recieved", + }); + } + } + } + } + + /// Get the [`ServerPort`] XML + pub async fn get_serverport(&self) -> Result { + let bcxml = self.get_services().await?; + if let BcXml { + server_port: Some(xml), + .. + } = bcxml + { + Ok(xml) + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected ServerPort xml but it was not recieved", + }) + } + } + + /// Set the server port + pub async fn set_serverport(&self, set_on: Option, set_port: Option) -> Result<()> { + let bcxml = self.get_services().await?; + if let BcXml { + server_port: Some(mut xml), + .. + } = bcxml + { + if let Some(enabled) = set_on { + xml.enable = Some({ + if enabled { + 1 + } else { + 0 + } + }); + } + if let Some(port) = set_port { + xml.port = port; + } + self.set_services(BcXml { + server_port: Some(xml), + ..Default::default() + }) + .await + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected ServerPort xml but it was not recieved", + }) + } + } + + /// Get the [`HttpPort`] XML + pub async fn get_http(&self) -> Result { + let bcxml = self.get_services().await?; + if let BcXml { + http_port: Some(xml), + .. + } = bcxml + { + Ok(xml) + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected HttpPort xml but it was not recieved", + }) + } + } + + /// Set the http port + pub async fn set_http(&self, set_on: Option, set_port: Option) -> Result<()> { + let bcxml = self.get_services().await?; + if let BcXml { + http_port: Some(mut xml), + .. + } = bcxml + { + if let Some(enabled) = set_on { + xml.enable = Some({ + if enabled { + 1 + } else { + 0 + } + }); + } + if let Some(port) = set_port { + xml.port = port; + } + self.set_services(BcXml { + http_port: Some(xml), + ..Default::default() + }) + .await + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected HttpPort xml but it was not recieved", + }) + } + } + + /// Get the [`HttpPort`] XML + pub async fn get_https(&self) -> Result { + let bcxml = self.get_services().await?; + if let BcXml { + https_port: Some(xml), + .. + } = bcxml + { + Ok(xml) + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected HttpsPort xml but it was not recieved", + }) + } + } + + /// Set the https port + pub async fn set_https(&self, set_on: Option, set_port: Option) -> Result<()> { + let bcxml = self.get_services().await?; + if let BcXml { + https_port: Some(mut xml), + .. + } = bcxml + { + if let Some(enabled) = set_on { + xml.enable = Some({ + if enabled { + 1 + } else { + 0 + } + }); + } + if let Some(port) = set_port { + xml.port = port; + } + self.set_services(BcXml { + https_port: Some(xml), + ..Default::default() + }) + .await + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected HttpsPort xml but it was not recieved", + }) + } + } + + /// Get the [`RtspPort`] XML + pub async fn get_rtsp(&self) -> Result { + let bcxml = self.get_services().await?; + if let BcXml { + rtsp_port: Some(xml), + .. + } = bcxml + { + Ok(xml) + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected RtspPort xml but it was not recieved", + }) + } + } + + /// Set the http port + pub async fn set_rtsp(&self, set_on: Option, set_port: Option) -> Result<()> { + let bcxml = self.get_services().await?; + if let BcXml { + rtsp_port: Some(mut xml), + .. + } = bcxml + { + if let Some(enabled) = set_on { + xml.enable = Some({ + if enabled { + 1 + } else { + 0 + } + }); + } + if let Some(port) = set_port { + xml.port = port; + } + self.set_services(BcXml { + rtsp_port: Some(xml), + ..Default::default() + }) + .await + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected RtspPort xml but it was not recieved", + }) + } + } + + /// Get the [`RtmpPort`] XML + pub async fn get_rtmp(&self) -> Result { + let bcxml = self.get_services().await?; + if let BcXml { + rtmp_port: Some(xml), + .. + } = bcxml + { + Ok(xml) + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected RtmpPort xml but it was not recieved", + }) + } + } + + /// Set the rtmp port + pub async fn set_rtmp(&self, set_on: Option, set_port: Option) -> Result<()> { + let bcxml = self.get_services().await?; + if let BcXml { + rtmp_port: Some(mut xml), + .. + } = bcxml + { + if let Some(enabled) = set_on { + xml.enable = Some({ + if enabled { + 1 + } else { + 0 + } + }); + } + if let Some(port) = set_port { + xml.port = port; + } + self.set_services(BcXml { + rtmp_port: Some(xml), + ..Default::default() + }) + .await + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected RtmpPort xml but it was not recieved", + }) + } + } + + /// Get the [`OnvifPort`] XML + pub async fn get_onvif(&self) -> Result { + let bcxml = self.get_services().await?; + if let BcXml { + onvif_port: Some(xml), + .. + } = bcxml + { + Ok(xml) + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected OnvifPort xml but it was not recieved", + }) + } + } + + /// Set the onvif port + pub async fn set_onvif(&self, set_on: Option, set_port: Option) -> Result<()> { + let bcxml = self.get_services().await?; + if let BcXml { + onvif_port: Some(mut xml), + .. + } = bcxml + { + if let Some(enabled) = set_on { + xml.enable = Some({ + if enabled { + 1 + } else { + 0 + } + }); + } + if let Some(port) = set_port { + xml.port = port; + } + self.set_services(BcXml { + onvif_port: Some(xml), + ..Default::default() + }) + .await + } else { + Err(Error::UnintelligibleXml { + reply: std::sync::Arc::new(Box::new(bcxml)), + why: "Expected OnvifPort xml but it was not recieved", + }) + } + } +} diff --git a/crates/core/src/bcudp/de.rs b/crates/core/src/bcudp/de.rs index a707973c..b1fff264 100644 --- a/crates/core/src/bcudp/de.rs +++ b/crates/core/src/bcudp/de.rs @@ -74,11 +74,11 @@ fn udp_disc(buf: &[u8]) -> IResult<&[u8], UdpDiscovery> { assert_eq!(checksum, actual_checksum); let decrypted_payload = decrypt(tid, enc_data_slice); - log::error!( - "decrypted_payload: {:?}", - std::str::from_utf8(&decrypted_payload) - ); let payload = UdpXml::try_parse(decrypted_payload.as_slice()).map_err(|e| { + log::error!( + "decrypted_payload: {:?}", + std::str::from_utf8(&decrypted_payload) + ); log::error!("e: {:?}", e); Err::Error(make_error( buf, diff --git a/src/cmdline.rs b/src/cmdline.rs index 0ae938c6..e37091b1 100644 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -27,4 +27,5 @@ pub enum Command { MqttRtsp(super::mqtt::Opt), Image(super::image::Opt), Battery(super::battery::Opt), + Services(super::services::Opt), } diff --git a/src/main.rs b/src/main.rs index 728b600b..fc0d5a8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,7 @@ mod pir; mod ptz; mod reboot; mod rtsp; +mod services; mod statusled; mod talk; mod utils; @@ -122,6 +123,9 @@ async fn main() -> Result<()> { Some(Command::Battery(opts)) => { battery::main(opts, neo_reactor.clone()).await?; } + Some(Command::Services(opts)) => { + services::main(opts, neo_reactor.clone()).await?; + } } Ok(()) diff --git a/src/services/cmdline.rs b/src/services/cmdline.rs new file mode 100644 index 00000000..93405f69 --- /dev/null +++ b/src/services/cmdline.rs @@ -0,0 +1,58 @@ +use anyhow::{anyhow, Result}; +use clap::{Parser, ValueEnum}; + +fn onoff_parse(src: &str) -> Result { + match src { + "true" | "on" | "yes" => Ok(true), + "false" | "off" | "no" => Ok(false), + _ => Err(anyhow!( + "Could not understand {}, check your input, should be true/false, on/off or yes/no", + src + )), + } +} + +/// The services command will control the ports for http/https/rtmp/rtsp/onvif +#[derive(Parser, Debug)] +pub struct Opt { + /// The name of the camera. Must be a name in the config + pub camera: String, + /// service to change + pub service: Services, + /// The action to perform + #[command(subcommand)] + pub cmd: PortAction, +} + +#[derive(Parser, Debug, Clone, ValueEnum)] +pub enum Services { + Baichuan, + Http, + Https, + Rtmp, + Rtsp, + Onvif, +} + +#[derive(Parser, Debug)] +pub enum PortAction { + /// Get the current details + Get, + /// Turn the service ON + On, + /// Turn the service OFF + Off, + /// Set both port and on/off + Set { + /// The new port + port: u32, + /// On/off + #[arg(value_parser = onoff_parse, action = clap::ArgAction::Set, name = "on|off")] + enabled: bool, + }, + /// Set the port + Port { + /// The new port + port: u32, + }, +} diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 00000000..1ba70033 --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1,260 @@ +/// +/// # Neolink Services +/// +/// This module handles the controls of the services like http/https/rtmp/rtsp/onvif +/// It only works on newer cameras that have these built in +/// +/// +/// # Usage +/// +/// ```bash +/// # To turn on http +/// neolink services --config=config.toml CameraName http on +/// # Or off +/// neolink services --config=config.toml CameraName http off +/// # Or set the port +/// neolink services --config=config.toml CameraName http port 80 +/// # Or get the current port details +/// neolink services --config=config.toml CameraName http get +/// ``` +/// +/// Services are +/// - http +/// - https +/// - rtmp +/// - rtsp +/// - onvif (will also turn on rtsp) +/// +use anyhow::{Context, Result}; + +mod cmdline; + +use crate::common::NeoReactor; +pub(crate) use cmdline::*; + +/// Entry point for the pir subcommand +/// +/// Opt is the command line options +pub(crate) async fn main(opt: Opt, reactor: NeoReactor) -> Result<()> { + let camera = reactor.get(&opt.camera).await?; + + match opt.cmd { + PortAction::Get => match opt.service { + Services::Http => { + let state = camera + .run_task(|cam| { + Box::pin(async move { + cam.get_http() + .await + .context("Unable to get camera service state") + }) + }) + .await?; + let ser = String::from_utf8( + { + let mut buf = bytes::BytesMut::new(); + quick_xml::se::to_writer(&mut buf, &state).map(|_| buf.to_vec()) + } + .expect("Should Ser the struct"), + ) + .expect("Should be UTF8"); + println!("{}", ser); + } + Services::Https => { + let state = camera + .run_task(|cam| { + Box::pin(async move { + cam.get_https() + .await + .context("Unable to get camera service state") + }) + }) + .await?; + let ser = String::from_utf8( + { + let mut buf = bytes::BytesMut::new(); + quick_xml::se::to_writer(&mut buf, &state).map(|_| buf.to_vec()) + } + .expect("Should Ser the struct"), + ) + .expect("Should be UTF8"); + println!("{}", ser); + } + Services::Rtsp => { + let state = camera + .run_task(|cam| { + Box::pin(async move { + cam.get_rtsp() + .await + .context("Unable to get camera service state") + }) + }) + .await?; + let ser = String::from_utf8( + { + let mut buf = bytes::BytesMut::new(); + quick_xml::se::to_writer(&mut buf, &state).map(|_| buf.to_vec()) + } + .expect("Should Ser the struct"), + ) + .expect("Should be UTF8"); + println!("{}", ser); + } + Services::Rtmp => { + let state = camera + .run_task(|cam| { + Box::pin(async move { + cam.get_rtmp() + .await + .context("Unable to get camera service state") + }) + }) + .await?; + let ser = String::from_utf8( + { + let mut buf = bytes::BytesMut::new(); + quick_xml::se::to_writer(&mut buf, &state).map(|_| buf.to_vec()) + } + .expect("Should Ser the struct"), + ) + .expect("Should be UTF8"); + println!("{}", ser); + } + Services::Onvif => { + let state = camera + .run_task(|cam| { + Box::pin(async move { + cam.get_onvif() + .await + .context("Unable to get camera service state") + }) + }) + .await?; + let ser = String::from_utf8( + { + let mut buf = bytes::BytesMut::new(); + quick_xml::se::to_writer(&mut buf, &state).map(|_| buf.to_vec()) + } + .expect("Should Ser the struct"), + ) + .expect("Should be UTF8"); + println!("{}", ser); + } + Services::Baichuan => { + let state = camera + .run_task(|cam| { + Box::pin(async move { + cam.get_serverport() + .await + .context("Unable to get camera service state") + }) + }) + .await?; + let ser = String::from_utf8( + { + let mut buf = bytes::BytesMut::new(); + quick_xml::se::to_writer(&mut buf, &state).map(|_| buf.to_vec()) + } + .expect("Should Ser the struct"), + ) + .expect("Should be UTF8"); + println!("{}", ser); + } + }, + action => { + let on = match &action { + PortAction::On => Some(true), + PortAction::Off => Some(false), + PortAction::Set { enabled, .. } => Some(*enabled), + _ => None, + }; + let port = match &action { + PortAction::Port { port } => Some(*port), + PortAction::Set { port, .. } => Some(*port), + _ => None, + }; + match opt.service { + Services::Http => { + camera + .run_task(|cam| { + Box::pin(async move { + cam.set_http(on, port) + .await + .context("Unable to set camera service state") + }) + }) + .await?; + } + Services::Https => { + camera + .run_task(|cam| { + Box::pin(async move { + cam.set_https(on, port) + .await + .context("Unable to set camera service state") + }) + }) + .await?; + } + Services::Rtsp => { + camera + .run_task(|cam| { + Box::pin(async move { + // Onvif will not work without rtsp + if let Some(false) = &on { + cam.set_onvif(Some(false), None) + .await + .context("Unable to set camera service state")?; + } + cam.set_rtsp(on, port) + .await + .context("Unable to set camera service state") + }) + }) + .await?; + } + Services::Rtmp => { + camera + .run_task(|cam| { + Box::pin(async move { + cam.set_rtmp(on, port) + .await + .context("Unable to set camera service state") + }) + }) + .await?; + } + Services::Onvif => { + camera + .run_task(|cam| { + Box::pin(async move { + // Onvif will not work without rtsp + if let Some(true) = &on { + cam.set_rtsp(Some(true), None) + .await + .context("Unable to set camera service state")?; + } + cam.set_onvif(on, port) + .await + .context("Unable to set camera service state") + }) + }) + .await?; + } + Services::Baichuan => { + camera + .run_task(|cam| { + Box::pin(async move { + cam.set_serverport(on, port) + .await + .context("Unable to set camera service state") + }) + }) + .await?; + } + } + } + } + + Ok(()) +}