use std::ops::ControlFlow;
use std::sync::atomic::{AtomicBool, Ordering};
use bacnet_client::client::BACnetClient;
use bacnet_client::log_reader::{LogRequester, unsigned_value};
use bacnet_services::read_range::{ReadRangeReply, ReadRangeRequest, ReadRangeValidation};
use bacnet_transport::port::TransportPort;
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use super::{Engine, Wanted, timed_out, unreachable};
use crate::format::{LogColumns, StateTexts};
use crate::limits::RequestLimits;
#[must_use = "a timed-out naming request is a timeout the caller may count"]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Naming {
#[default]
Answered,
TimedOut,
}
impl Naming {
pub fn timed_out(self) -> bool {
self == Self::TimedOut
}
fn of(error: &Error) -> Self {
if timed_out(error) {
Self::TimedOut
} else {
Self::Answered
}
}
}
pub(super) struct PagedLog<'a, T: TransportPort + 'static> {
pub(super) client: &'a BACnetClient<T>,
pub(super) limits: &'a RequestLimits,
pub(super) device: u32,
pub(super) network: Option<u16>,
pub(super) first: AtomicBool,
}
impl<T: TransportPort + 'static> PagedLog<'_, T> {
async fn charge(&self) {
if !self.first.swap(false, Ordering::Relaxed) {
self.limits.slot(self.network).await;
}
}
}
impl<T: TransportPort + 'static> LogRequester for PagedLog<'_, T> {
async fn read_unsigned(
&self,
_mac: &[u8],
log: ObjectIdentifier,
property: PropertyIdentifier,
) -> Result<u64, Error> {
self.charge().await;
let ack = self
.client
.read_property_from_device(self.device, log, property, None)
.await?;
unsigned_value(&ack.property_value)
}
async fn read_range_lenient(
&self,
_mac: &[u8],
request: &ReadRangeRequest,
) -> Result<ReadRangeReply, Error> {
self.charge().await;
self.client
.read_range_from_device_with(self.device, request, ReadRangeValidation::Lenient)
.await
}
}
impl<T: TransportPort + 'static> Engine<T> {
pub async fn log_columns(&self, device: u32, log: ObjectIdentifier) -> (LogColumns, Naming) {
if !LogColumns::applies(log.object_type()) {
return (LogColumns::default(), Naming::Answered);
}
match self
.read_property(
device,
log,
PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY,
None,
)
.await
{
Ok(data) => (LogColumns::decode(&data), Naming::Answered),
Err(error) => (LogColumns::default(), Naming::of(&error)),
}
}
pub async fn log_state_texts(
&self,
device: u32,
columns: &mut LogColumns,
out_of_time: impl Fn() -> bool,
) -> Naming {
let out_of_time = &out_of_time;
let lost = AtomicBool::new(false);
let lost = &lost;
columns
.read_state_texts(Some(device), |object| async move {
if out_of_time() {
return ControlFlow::Break(());
}
let wanted = Wanted::List(StateTexts::properties(object.object_type()));
match self.read(device, object, wanted).await {
Ok(read) => ControlFlow::Continue(StateTexts::from_rows(&read.rows)),
Err(error) if unreachable(&error) => {
lost.store(timed_out(&error), Ordering::Relaxed);
ControlFlow::Break(())
}
Err(_) => ControlFlow::Continue(StateTexts::default()),
}
})
.await;
if lost.load(Ordering::Relaxed) {
Naming::TimedOut
} else {
Naming::Answered
}
}
}