use bacnet_client::client::BACnetClient;
use bacnet_encoding::primitives::decode_application_value;
use bacnet_transport::port::TransportPort;
use bacnet_types::constructed::{PropertyReference, ReadAccessSpecification};
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::primitives::ObjectIdentifier;
use crate::output::{self, OutputFormat};
use crate::parse;
pub(super) fn format_application_values(data: &[u8]) -> (Vec<String>, Option<String>) {
let mut offset = 0;
let mut values = Vec::new();
while offset < data.len() {
match decode_application_value(data, offset) {
Ok((value, next)) => {
values.push(output::format_property_value(&value));
offset = next;
}
Err(_) => return (values, Some(hex(&data[offset..]))),
}
}
(values, None)
}
pub(super) fn decode_and_format(data: &[u8]) -> String {
let (mut values, undecoded) = format_application_values(data);
if let Some(hex) = undecoded {
values.push(format!("[raw: {hex}]"));
}
values.join(", ")
}
pub(super) fn hex(data: &[u8]) -> String {
data.iter()
.map(|b| format!("{b:02x}"))
.collect::<Vec<_>>()
.join(" ")
}
pub async fn read_property_cmd<T: TransportPort + 'static>(
client: &BACnetClient<T>,
mac: &[u8],
object_type: ObjectType,
instance: u32,
property: PropertyIdentifier,
index: Option<u32>,
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let oid = ObjectIdentifier::new(object_type, instance)?;
if property == PropertyIdentifier::ALL {
return read_all_properties(client, mac, object_type, instance, format).await;
}
let ack = client.read_property(mac, oid, property, index).await?;
let decoded = decode_and_format(&ack.property_value);
output::print_read_result(
&format!(
"{}:{}",
ack.object_identifier.object_type(),
ack.object_identifier.instance_number()
),
&format!("{}", ack.property_identifier),
ack.property_array_index,
&decoded,
format,
);
Ok(())
}
async fn read_all_properties<T: TransportPort + 'static>(
client: &BACnetClient<T>,
mac: &[u8],
object_type: ObjectType,
instance: u32,
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let oid = ObjectIdentifier::new(object_type, instance)?;
let specs = vec![ReadAccessSpecification {
object_identifier: oid,
list_of_property_references: vec![PropertyReference {
property_identifier: PropertyIdentifier::ALL,
property_array_index: None,
}],
}];
let ack = client.read_property_multiple(mac, specs).await?;
print_rpm_results(&ack.list_of_read_access_results, format);
Ok(())
}
pub async fn read_multiple_cmd<T: TransportPort + 'static>(
client: &BACnetClient<T>,
mac: &[u8],
specs: &[String],
format: OutputFormat,
) -> Result<(), Box<dyn std::error::Error>> {
let mut access_specs = Vec::new();
let mut i = 0;
while i < specs.len() {
let (obj_type, instance) = parse::parse_object_specifier(&specs[i])?;
let oid = ObjectIdentifier::new(obj_type, instance)?;
let mut props = Vec::new();
if i + 1 < specs.len() {
i += 1;
for prop_str in specs[i].split(',') {
let (prop, idx) = parse::parse_property(prop_str.trim())?;
props.push(PropertyReference {
property_identifier: prop,
property_array_index: idx,
});
}
} else {
eprintln!(
"Note: no properties specified for {}:{}, defaulting to present-value",
obj_type, instance
);
props.push(PropertyReference {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
});
}
access_specs.push(ReadAccessSpecification {
object_identifier: oid,
list_of_property_references: props,
});
i += 1;
}
let ack = client.read_property_multiple(mac, access_specs).await?;
print_rpm_results(&ack.list_of_read_access_results, format);
Ok(())
}
fn print_rpm_results(results: &[bacnet_services::rpm::ReadAccessResult], format: OutputFormat) {
let mut entries = Vec::new();
for result in results {
let obj_str = format!(
"{}:{}",
result.object_identifier.object_type(),
result.object_identifier.instance_number()
);
for elem in &result.list_of_results {
let value_str = if let Some(ref value_bytes) = elem.property_value {
decode_and_format(value_bytes)
} else if let Some((class, code)) = elem.error {
format!("ERROR: {class}:{code}")
} else {
"???".to_string()
};
entries.push((
obj_str.clone(),
format!("{}", elem.property_identifier),
elem.property_array_index,
value_str,
));
}
}
output::print_rpm_table(&entries, format);
}