use bacnet_transport::port::TransportPort;
use bacnet_types::enums::{ErrorClass, PropertyIdentifier};
use bacnet_types::error::{Error, RemoteError};
use bacnet_types::primitives::ObjectIdentifier;
use crate::engine::{Halt, Reader, Wanted};
use crate::format::{RawRow, StateTexts};
pub async fn first_read<T: TransportPort + 'static>(
reader: &mut Reader<'_, T>,
object: ObjectIdentifier,
wanted: &[PropertyIdentifier],
name: bool,
texts: bool,
) -> Result<Vec<RawRow>, Halt> {
let named = name && !wanted.contains(&PropertyIdentifier::OBJECT_NAME);
let with_name = [wanted, &[PropertyIdentifier::OBJECT_NAME]].concat();
let first_ask = if named { &with_name[..] } else { wanted };
let mut read = reader.properties(object, Wanted::List(first_ask)).await;
if let (true, Err(Halt::Device(error))) = (named, &read)
&& name_may_explain(error)
{
tracing::debug!(
"{object}: first read failed ({error}); reading it again without Object_Name"
);
read = reader.properties(object, Wanted::List(wanted)).await;
}
let (mut rows, _) = read?;
let watched = wanted.first();
if texts && rows.iter().any(|(p, _)| Some(p) == watched) {
let extra = state_texts(reader, object, wanted).await;
rows.extend(extra);
}
Ok(rows)
}
pub fn name_may_explain(error: &Error) -> bool {
match error {
Error::Remote(RemoteError::Error { class, .. }) => *class == ErrorClass::PROPERTY,
Error::Decoding(_) => true,
_ => false,
}
}
async fn state_texts<T: TransportPort + 'static>(
reader: &mut Reader<'_, T>,
object: ObjectIdentifier,
wanted: &[PropertyIdentifier],
) -> Vec<RawRow> {
let texts: Vec<PropertyIdentifier> = StateTexts::properties(object.object_type())
.iter()
.copied()
.filter(|property| !wanted.contains(property))
.collect();
if texts.is_empty() {
return Vec::new();
}
match reader.properties(object, Wanted::List(&texts)).await {
Ok((rows, _)) => rows,
Err(error) => {
tracing::debug!("{object}: state texts not read ({error:?}); values show without them");
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use bacnet_types::enums::{AbortReason, ErrorCode, RejectReason};
use super::*;
#[test]
fn only_a_property_error_or_a_bad_answer_retries_without_the_name() {
let error = |class| {
Error::Remote(RemoteError::Error {
class,
code: ErrorCode::UNKNOWN_PROPERTY,
})
};
assert!(name_may_explain(&error(ErrorClass::PROPERTY)));
assert!(name_may_explain(&Error::decoding(3, "read access result")));
assert!(!name_may_explain(&error(ErrorClass::OBJECT)));
assert!(!name_may_explain(&Error::reject(
RejectReason::UNRECOGNIZED_SERVICE
)));
assert!(!name_may_explain(&Error::abort(AbortReason::TSM_TIMEOUT)));
assert!(!name_may_explain(&Error::Timeout(Duration::from_secs(6))));
}
}