use bacnet_objects::database::ObjectDatabase;
use bacnet_transport::mstp::{MstpConfig, MstpExecutionMode, MstpTransport, SerialPort};
use bacnet_types::error::Error;
use crate::session::{EndpointSession, SessionConfig, SessionRole};
const MSTP_MAX_APDU: u16 = 480;
pub struct MstpEndpointBuilder<S: SerialPort> {
serial: Option<S>,
this_station: u8,
max_master: u8,
max_info_frames: u8,
baud_rate: u32,
execution_mode: MstpExecutionMode,
role: SessionRole,
session: SessionConfig,
database: Option<ObjectDatabase>,
identity: Option<crate::identity::DeviceIdentity>,
}
impl<S: SerialPort> MstpEndpointBuilder<S> {
pub fn new(serial: S, this_station: u8) -> Self {
Self {
serial: Some(serial),
this_station,
max_master: 127,
max_info_frames: 1,
baud_rate: 9600,
execution_mode: MstpExecutionMode::default(),
role: SessionRole::Both,
session: SessionConfig::default(),
database: None,
identity: None,
}
}
pub fn max_master(mut self, max_master: u8) -> Self {
self.max_master = max_master;
self
}
pub fn max_info_frames(mut self, max_info_frames: u8) -> Self {
self.max_info_frames = max_info_frames;
self
}
pub fn baud_rate(mut self, baud_rate: u32) -> Self {
self.baud_rate = baud_rate;
self
}
pub fn execution_mode(mut self, mode: MstpExecutionMode) -> Self {
self.execution_mode = mode;
self
}
pub fn role(mut self, role: SessionRole) -> Self {
self.role = role;
self
}
pub fn queue_capacity(mut self, capacity: usize) -> Self {
self.session.queue_capacity = capacity;
self
}
pub fn read_work_limit(mut self, limit: usize) -> Self {
self.session.read_work_limit = limit;
self
}
pub fn client_timers(mut self, timeout_ms: u64, retries: u8) -> Self {
self.session.apdu_timeout_ms = timeout_ms;
self.session.apdu_retries = retries;
self
}
pub fn database(mut self, db: ObjectDatabase) -> Self {
self.database = Some(db);
self
}
pub fn identity(mut self, identity: crate::identity::DeviceIdentity) -> Self {
self.identity = Some(identity);
self
}
pub fn validate_only(&self) -> Result<(), Error> {
Self::validate_addressing(self.this_station, self.max_master)
}
fn validate_addressing(this_station: u8, max_master: u8) -> Result<(), Error> {
if max_master > 127 {
return Err(Error::Encoding(format!(
"MS/TP endpoint: max_master {max_master} exceeds MAX_MASTER (127)"
)));
}
if this_station > max_master {
return Err(Error::Encoding(format!(
"MS/TP endpoint: this_station {this_station} exceeds configured max_master ({max_master})"
)));
}
Ok(())
}
pub fn build_transport(mut self) -> Result<MstpTransport<S>, Error> {
Self::validate_addressing(self.this_station, self.max_master)?;
let serial = self
.serial
.take()
.ok_or_else(|| Error::Encoding("MS/TP endpoint serial owner is missing".into()))?;
let config = MstpConfig {
this_station: self.this_station,
max_master: self.max_master,
max_info_frames: self.max_info_frames,
baud_rate: self.baud_rate,
};
Ok(MstpTransport::new(serial, config).with_execution_mode(self.execution_mode))
}
pub fn build_session(mut self) -> Result<EndpointSession<MstpTransport<S>>, Error> {
Self::validate_addressing(self.this_station, self.max_master)?;
if let Some(identity) = self.identity.as_ref() {
if identity.max_apdu_length() > MSTP_MAX_APDU {
return Err(Error::Encoding(format!(
"MS/TP endpoint: identity max-APDU {} exceeds MS/TP transport bound {MSTP_MAX_APDU}",
identity.max_apdu_length()
)));
}
}
let role = self.role;
let session = self.session.clone();
let database = self.database.take();
let identity = self.identity.take();
let transport = self.build_transport()?;
let mut endpoint = EndpointSession::new(transport, role, session)?;
if let Some(db) = database {
endpoint = endpoint.with_database(db);
}
if let Some(id) = identity {
endpoint = endpoint.with_identity(id);
}
Ok(endpoint)
}
}