Skip to content

Commit

Permalink
Merge pull request #42 from QuantumEntangledAndy/battery
Browse files Browse the repository at this point in the history
Battery message and requests
  • Loading branch information
QuantumEntangledAndy authored Mar 3, 2023
2 parents e04c2a1 + 92447b6 commit 70e6217
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ description = "A standards-compliant bridge to Reolink IP cameras"
version = "0.5.4"
authors = ["George Hilliard <[email protected]>", "Andrew King <[email protected]>"]
edition = "2018"
default-run = "neolink"

[workspace]
members = [
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ Control messages:
Status Messages:
`/status offline` Sent when the neolink goes offline this is a LastWill message
`/status disconnected` Sent when the camera goes offline
`/status/battery` Sent in reply to a `/query/battery`
`/status/battery` Sent in reply to a `/query/battery` send an XML encoded version of the battery status

Query Messages:
`/query/battery` Request that the camera reports its battery level (Not Yet Implemented)
`/query/battery` Request that the camera reports its battery level

### Pause

Expand Down
1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ md5 = "0.7"
nom = { version = "6.1.2", features = ["alloc"] }
rand = "0.8.4"
regex = "1.5.4"
serde = { version = "1.0", features = ["derive"] }
socket2 = "0.3"
time = "0.2"
tokio = { version = "1.24.2", features = ["full"] }
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 @@ -47,6 +47,10 @@ pub const MSG_ID_GET_LED_STATUS: u32 = 208;
pub const MSG_ID_SET_LED_STATUS: u32 = 209;
/// UDP Keep alive
pub const MSG_ID_UDP_KEEP_ALIVE: u32 = 234;
/// Battery message initiaed by the camera
pub const MSG_ID_BATTERY_INFO_LIST: u32 = 252;
/// Battery message initiaed by the client
pub const MSG_ID_BATTERY_INFO: u32 = 253;

/// An empty password in legacy format
pub const EMPTY_LEGACY_PASSWORD: &str =
Expand Down
47 changes: 47 additions & 0 deletions crates/core/src/bc/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ pub struct BcXml {
/// Sent to move the camera
#[yaserde(rename = "PtzControl")]
pub ptz_control: Option<PtzControl>,
/// Recieved on login/low battery events
#[yaserde(rename = "BatteryList")]
pub battery_list: Option<BatteryList>,
/// Recieved on request for battery info
#[yaserde(rename = "BatteryInfo")]
pub battery_info: Option<BatteryInfo>,
}

impl BcXml {
Expand Down Expand Up @@ -463,6 +469,47 @@ pub struct PtzControl {
pub command: String,
}

/// A list of battery infos. This message is sent from the camera as
/// an event
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
pub struct BatteryList {
/// XML Version
#[yaserde(attribute)]
pub version: String,
/// Battery info items
#[yaserde(rename = "BatteryInfo")]
pub battery_info: Vec<BatteryInfo>,
}

/// The individual battery info
#[derive(PartialEq, Eq, Default, Debug, YaDeserialize, YaSerialize)]
pub struct BatteryInfo {
/// The channel the for the camera usually 0
#[yaserde(rename = "channelId")]
pub channel_id: u8,
/// Charge status known values, "chargeComplete", "charging", "none",
#[yaserde(rename = "chargeStatus")]
pub charge_status: String,
/// Status of charging port known values: "solarPanel"
#[yaserde(rename = "adapterStatus")]
pub adapter_status: String,
/// Voltage
pub voltage: i32,
/// Current
pub current: i32,
/// Temperture
pub temperature: i32,
/// % charge from 0-100
#[yaserde(rename = "batteryPercent")]
pub battery_percent: u32,
/// Low power flag. Known values 0, 1 (0=false)
#[yaserde(rename = "lowPower")]
pub low_power: u32,
/// Battery version info: Known values 2
#[yaserde(rename = "batteryVersion")]
pub battery_version: u32,
}

/// Convience function to return the xml version used throughout the library
pub fn xml_ver() -> String {
"1.1".to_string()
Expand Down
33 changes: 32 additions & 1 deletion crates/core/src/bc_protocol.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::bc;
use futures::stream::StreamExt;
use log::*;
use serde::{Deserialize, Serialize};
use std::net::ToSocketAddrs;
use std::sync::atomic::{AtomicBool, AtomicU16, Ordering};

use Md5Trunc::*;

mod battery;
mod connection;
mod credentials;
mod errors;
Expand Down Expand Up @@ -50,6 +52,20 @@ pub struct BcCamera {
credentials: Credentials,
}

/// Used to choode the print format of various status messages like battery levels
///
/// Currently this is just the format of battery levels but if we ever got more status
/// messages then they will also use this information
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PrintFormat {
/// None, don't print
None,
/// A human readable output
Human,
/// Xml formatted
Xml,
}

impl BcCamera {
///
/// Create a new camera interface with this address and channel ID
Expand All @@ -69,6 +85,7 @@ impl BcCamera {
channel_id: u8,
username: V,
passwd: Option<W>,
aux_info_format: PrintFormat,
) -> Result<Self> {
let username: String = username.into();
let passwd: Option<String> = passwd.map(|t| t.into());
Expand All @@ -82,6 +99,7 @@ impl BcCamera {
channel_id,
&username,
passwd.as_ref(),
aux_info_format,
)
.await
{
Expand Down Expand Up @@ -111,12 +129,14 @@ impl BcCamera {
username: U,
passwd: Option<V>,
discovery_method: DiscoveryMethods,
aux_info_format: PrintFormat,
) -> Result<Self> {
Self::new(
SocketAddrOrUid::Uid(uid.to_string(), discovery_method),
channel_id,
username,
passwd,
aux_info_format,
)
.await
}
Expand Down Expand Up @@ -146,6 +166,7 @@ impl BcCamera {
channel_id: u8,
username: V,
passwd: Option<W>,
aux_info_format: PrintFormat,
) -> Result<Self> {
let addr_iter = match host.to_socket_addrs_or_uid() {
Ok(iter) => iter,
Expand All @@ -154,7 +175,15 @@ impl BcCamera {
let username: String = username.into();
let passwd: Option<String> = passwd.map(|t| t.into());
for addr_or_uid in addr_iter {
if let Ok(cam) = Self::new(addr_or_uid, channel_id, &username, passwd.as_ref()).await {
if let Ok(cam) = Self::new(
addr_or_uid,
channel_id,
&username,
passwd.as_ref(),
aux_info_format,
)
.await
{
return Ok(cam);
}
}
Expand Down Expand Up @@ -184,6 +213,7 @@ impl BcCamera {
channel_id: u8,
username: U,
passwd: Option<V>,
aux_info_format: PrintFormat,
) -> Result<Self> {
let username: String = username.into();
let passwd: Option<String> = passwd.map(|t| t.into());
Expand Down Expand Up @@ -306,6 +336,7 @@ impl BcCamera {
credentials: Credentials::new(username, passwd),
};
me.keepalive().await?;
me.monitor_battery(aux_info_format).await?;
Ok(me)
}

Expand Down
130 changes: 130 additions & 0 deletions crates/core/src/bc_protocol/battery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! Handles battery related messages
//!
//! There are primarily two messages:
//! - BatteryInfoList which the camera sends as part of its login info
//! - BatteryInfo which the client can request on demand
//!
use super::{BcCamera, PrintFormat, Result};
use crate::{
bc::{model::*, xml::BatteryInfo},
Error,
};

impl BcCamera {
/// Create a handller to respond to battery messages
/// These messages are sent by the camera on login and maybe
/// also on low battery events
pub async fn monitor_battery(&self, format: PrintFormat) -> Result<()> {
let connection = self.get_connection();
connection
.handle_msg(MSG_ID_BATTERY_INFO_LIST, move |bc| {
if let Bc {
body:
BcBody::ModernMsg(ModernMsg {
payload:
Some(BcPayloads::BcXml(BcXml {
battery_list: Some(battery_list),
..
})),
..
}),
..
} = bc
{
for battery in battery_list.battery_info.iter() {
match format {
PrintFormat::None => {}
PrintFormat::Human => {
println!(
"==Battery==\n\
Charge: {}%,\n\
Temperature: {}°C,\n\
LowPower: {},\n\
Adapter: {},\n\
ChargeStatus: {},\n\
",
battery.battery_percent,
battery.temperature,
if battery.low_power == 1 {
"true"
} else {
"false"
},
battery.adapter_status,
battery.charge_status,
);
}
PrintFormat::Xml => {
let bat_ser = String::from_utf8(
yaserde::ser::serialize_with_writer(
battery,
vec![],
&Default::default(),
)
.expect("Should Ser the struct"),
)
.expect("Should be UTF8");
println!("{}", bat_ser);
}
}
}
}
None
})
.await?;
Ok(())
}

/// Requests the current battery status of the camera
pub async fn battery_info(&self) -> Result<BatteryInfo> {
let connection = self.get_connection();

let msg_num = self.new_message_num();
let mut sub = connection.subscribe(msg_num).await?;

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

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

if let Bc {
meta: BcMeta {
response_code: 200, ..
},
body:
BcBody::ModernMsg(ModernMsg {
payload:
Some(BcPayloads::BcXml(BcXml {
battery_info: Some(battery_info),
..
})),
..
}),
} = msg
{
Ok(battery_info)
} else {
Err(Error::UnintelligibleReply {
reply: std::sync::Arc::new(Box::new(msg)),
why: "The camera did not accept the battery info (maybe no battery) command.",
})
}
}
}
12 changes: 12 additions & 0 deletions sample_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ address = "192.168.1.187:9000"
#
# discovery = "relay"

# Certain types of camera emit status messages (such as battery levels)
#
# By default we hide these status messages from the user but you can instead requst that
# they be printed to stdout using print_format
#
# Valid values are:
# - None
# - Human
# - Xml
#
# print_format = "None"


[[cameras]]
name = "storage shed"
Expand Down
8 changes: 8 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use lazy_static::lazy_static;
use neolink_core::bc_protocol::PrintFormat;
use regex::Regex;
use serde::Deserialize;
use std::clone::Clone;
Expand Down Expand Up @@ -99,6 +100,9 @@ pub(crate) struct CameraConfig {
#[serde(default = "default_strict")]
/// If strict then the media stream will error in the event that the media packets are not as expected
pub(crate) strict: bool,

#[serde(default = "default_print", alias = "print")]
pub(crate) print_format: PrintFormat,
}

#[derive(Debug, Deserialize, Validate, Clone)]
Expand Down Expand Up @@ -143,6 +147,10 @@ fn default_mqtt() -> Option<MqttConfig> {
None
}

fn default_print() -> PrintFormat {
PrintFormat::None
}

fn default_discovery() -> String {
"Relay".to_string()
}
Expand Down
Loading

0 comments on commit 70e6217

Please sign in to comment.