#[derive(Debug, PartialEq, Eq, Default)]
pub struct PeripheralOptions<'a> {
pub ident_number: u16,
pub sync_mode: bool,
pub freeze_mode: bool,
pub groups: u8,
pub max_tsdr: u16,
pub fail_safe: bool,
pub user_parameters: Option<&'a [u8]>,
pub config: Option<&'a [u8]>,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DiagnosticFlags: u16 {
const STATION_NOT_READY = 0b00000010;
const CONFIGURATION_FAULT = 0b00000100;
const EXT_DIAG = 0b00001000;
const NOT_SUPPORTED = 0b00010000;
const PARAMETER_FAULT = 0b01000000;
const PARAMETER_REQUIRED = 0b00000001_00000000;
const STATUS_DIAGNOSTICS = 0b00000010_00000000;
const PERMANENT_BIT = 0b00000100_00000000;
const WATCHDOG_ON = 0b00001000_00000000;
const FREEZE_MODE = 0b00010000_00000000;
const SYNC_MODE = 0b00100000_00000000;
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
pub enum PeripheralEvent {
Online,
Configured,
ConfigError,
ParameterError,
DataExchanged,
Diagnostics,
Offline,
}
#[derive(Clone, Debug)]
pub struct PeripheralDiagnostics<'a> {
pub flags: DiagnosticFlags,
pub ident_number: u16,
pub master_address: Option<u8>,
pub extended_diagnostics: &'a crate::dp::ExtendedDiagnostics<'a>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct DiagnosticsInfo {
pub flags: DiagnosticFlags,
pub ident_number: u16,
pub master_address: Option<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
enum PeripheralState {
#[default]
Offline,
WaitForParam,
WaitForConfig,
ValidateConfig,
PreDataExchange,
DataExchange,
}
#[derive(Debug)]
pub struct Peripheral<'a> {
address: u8,
state: PeripheralState,
retry_count: u8,
fcb: crate::fdl::FrameCountBit,
pi_i: managed::ManagedSlice<'a, u8>,
pi_q: managed::ManagedSlice<'a, u8>,
diag: Option<DiagnosticsInfo>,
ext_diag: crate::dp::ExtendedDiagnostics<'a>,
diag_needed: bool,
#[cfg(feature = "debug-measure-roundtrip")]
tx_time: Option<crate::time::Instant>,
options: PeripheralOptions<'a>,
}
impl Default for Peripheral<'_> {
fn default() -> Self {
Self {
address: Default::default(),
state: Default::default(),
retry_count: Default::default(),
fcb: Default::default(),
pi_i: managed::ManagedSlice::Borrowed(&mut []),
pi_q: managed::ManagedSlice::Borrowed(&mut []),
diag: Default::default(),
ext_diag: Default::default(),
diag_needed: Default::default(),
#[cfg(feature = "debug-measure-roundtrip")]
tx_time: Default::default(),
options: Default::default(),
}
}
}
impl<'a> Peripheral<'a> {
pub fn new<PII, PIQ>(address: u8, options: PeripheralOptions<'a>, pi_i: PII, pi_q: PIQ) -> Self
where
PII: Into<managed::ManagedSlice<'a, u8>>,
PIQ: Into<managed::ManagedSlice<'a, u8>>,
{
Self {
address,
options,
pi_i: pi_i.into(),
pi_q: pi_q.into(),
..Default::default()
}
}
pub fn with_diag_buffer<S>(mut self, ext_diag: S) -> Self
where
S: Into<managed::ManagedSlice<'a, u8>>,
{
self.ext_diag = crate::dp::ExtendedDiagnostics::from_buffer(ext_diag.into());
self
}
pub fn reset_address(&mut self, new_address: crate::Address) {
let options = core::mem::take(&mut self.options);
let pi_i = core::mem::replace(&mut self.pi_i, managed::ManagedSlice::Borrowed(&mut []));
let pi_q = core::mem::replace(&mut self.pi_q, managed::ManagedSlice::Borrowed(&mut []));
let diag_buffer = self.ext_diag.take_buffer();
*self = Self::new(new_address, options, pi_i, pi_q).with_diag_buffer(diag_buffer);
}
#[inline(always)]
pub fn address(&self) -> u8 {
self.address
}
#[inline(always)]
pub fn options(&self) -> &PeripheralOptions<'a> {
&self.options
}
#[inline(always)]
pub fn pi_i(&self) -> &[u8] {
&self.pi_i
}
#[inline(always)]
pub fn pi_q(&self) -> &[u8] {
&self.pi_q
}
#[inline(always)]
pub fn pi_q_mut(&mut self) -> &mut [u8] {
&mut self.pi_q
}
pub fn pi_both(&mut self) -> (&[u8], &mut [u8]) {
(&self.pi_i, &mut self.pi_q)
}
#[inline(always)]
pub fn is_live(&self) -> bool {
self.state != PeripheralState::Offline
}
#[inline(always)]
pub fn is_running(&self) -> bool {
self.state == PeripheralState::DataExchange
}
#[inline]
pub fn last_diagnostics(&self) -> Option<PeripheralDiagnostics> {
self.diag.as_ref().map(|diag| PeripheralDiagnostics {
flags: diag.flags,
ident_number: diag.ident_number,
master_address: diag.master_address,
extended_diagnostics: &self.ext_diag,
})
}
#[inline]
pub fn request_diagnostics(&mut self) {
self.diag_needed = true;
}
}
impl<'a> Peripheral<'a> {
pub(crate) fn transmit_telegram<'b>(
&mut self,
now: crate::time::Instant,
dp: &crate::dp::DpMasterState,
fdl: &crate::fdl::FdlActiveStation,
tx: crate::fdl::TelegramTx<'b>,
high_prio_only: bool,
) -> Result<crate::fdl::TelegramTxResponse, (crate::fdl::TelegramTx<'b>, Option<PeripheralEvent>)>
{
debug_assert!(dp.operating_state.is_operate() || dp.operating_state.is_clear());
if self.state != PeripheralState::Offline && self.retry_count == 1 {
log::warn!("Resending a telegram to #{}...", self.address);
}
let res = match self.state {
_ if self.retry_count > fdl.parameters().max_retry_limit => {
log::warn!("Peripheral #{} stopped responding!", self.address);
self.state = PeripheralState::Offline;
Err((tx, Some(PeripheralEvent::Offline)))
}
PeripheralState::Offline => {
if self.retry_count == 0 {
Ok(self.send_diagnostics_request(fdl, tx))
} else {
Err((tx, None))
}
}
PeripheralState::WaitForParam => {
if let Some(user_parameters) = self.options.user_parameters {
Ok(tx.send_data_telegram(
crate::fdl::DataTelegramHeader {
da: self.address,
sa: fdl.parameters().address,
dsap: crate::consts::SAP_SLAVE_SET_PRM,
ssap: crate::consts::SAP_MASTER_MS0,
fc: crate::fdl::FunctionCode::new_srd_low(self.fcb),
},
7 + user_parameters.len(),
|buf| {
buf[0] |= 0x80; if self.options.sync_mode {
buf[0] |= 0x20; }
if self.options.freeze_mode {
buf[0] |= 0x10; }
if let Some((f1, f2)) = fdl.parameters().watchdog_factors {
buf[0] |= 0x08; buf[1] = f1;
buf[2] = f2;
}
buf[3] = fdl.parameters().min_tsdr_bits;
buf[4..6].copy_from_slice(&self.options.ident_number.to_be_bytes());
buf[6] = self.options.groups;
buf[7..].copy_from_slice(&user_parameters);
},
))
} else {
Err((tx, None))
}
}
PeripheralState::WaitForConfig => {
if let Some(config) = self.options.config {
Ok(tx.send_data_telegram(
crate::fdl::DataTelegramHeader {
da: self.address,
sa: fdl.parameters().address,
dsap: crate::consts::SAP_SLAVE_CHK_CFG,
ssap: crate::consts::SAP_MASTER_MS0,
fc: crate::fdl::FunctionCode::new_srd_low(self.fcb),
},
config.len(),
|buf| {
buf.copy_from_slice(&config);
},
))
} else {
Err((tx, None))
}
}
PeripheralState::ValidateConfig => {
Ok(self.send_diagnostics_request(fdl, tx))
}
PeripheralState::DataExchange | PeripheralState::PreDataExchange => {
if self.diag_needed {
Ok(self.send_diagnostics_request(fdl, tx))
} else {
#[cfg(feature = "debug-measure-roundtrip")]
{
self.tx_time = Some(now);
}
Ok(tx.send_data_telegram(
crate::fdl::DataTelegramHeader {
da: self.address,
sa: fdl.parameters().address,
dsap: crate::consts::SAP_SLAVE_DATA_EXCHANGE,
ssap: crate::consts::SAP_MASTER_DATA_EXCHANGE,
fc: crate::fdl::FunctionCode::new_srd_high(self.fcb),
},
self.pi_q.len(),
|buf| {
if dp.operating_state.is_operate() {
buf.copy_from_slice(&self.pi_q);
}
},
))
}
}
};
if res.is_ok() {
self.retry_count += 1;
} else {
self.retry_count = 0;
}
res
}
pub(crate) fn receive_reply(
&mut self,
now: crate::time::Instant,
dp: &crate::dp::DpMasterState,
fdl: &crate::fdl::FdlActiveStation,
telegram: crate::fdl::Telegram,
) -> Option<PeripheralEvent> {
match self.state {
PeripheralState::Offline => {
if self.handle_diagnostics_response(fdl, &telegram).is_some() {
self.retry_count = 0;
self.state = PeripheralState::WaitForParam;
Some(PeripheralEvent::Online)
} else {
None
}
}
PeripheralState::WaitForParam => {
if let crate::fdl::Telegram::ShortConfirmation(_) = telegram {
log::debug!("Sent parameters to #{}.", self.address);
self.fcb.cycle();
self.state = PeripheralState::WaitForConfig;
self.retry_count = 0;
None
} else {
log::warn!("Unexpected response after sending parameters: {telegram:?}");
None
}
}
PeripheralState::WaitForConfig => {
if let crate::fdl::Telegram::ShortConfirmation(_) = telegram {
log::debug!("Sent configuration to #{}.", self.address);
self.fcb.cycle();
self.state = PeripheralState::ValidateConfig;
self.retry_count = 0;
None
} else {
log::warn!("Unexpected response after sending config: {telegram:?}");
None
}
}
PeripheralState::ValidateConfig => {
let address = self.address;
self.retry_count = 0;
let (new_state, event) =
if let Some(diag) = self.handle_diagnostics_response(fdl, &telegram) {
if diag.flags.contains(DiagnosticFlags::PARAMETER_FAULT) {
log::warn!("Peripheral #{} reports a parameter fault!", address);
(
PeripheralState::Offline,
Some(PeripheralEvent::ParameterError),
)
} else if diag.flags.contains(DiagnosticFlags::CONFIGURATION_FAULT) {
log::warn!("Peripheral #{} reports a configuration fault!", address);
(PeripheralState::Offline, Some(PeripheralEvent::ConfigError))
} else if diag.flags.contains(DiagnosticFlags::PARAMETER_REQUIRED) {
log::warn!(
"Peripheral #{} wants parameters after completing setup?! Retrying...",
address
);
(PeripheralState::WaitForParam, None)
} else if !diag.flags.contains(DiagnosticFlags::STATION_NOT_READY) {
log::info!("Peripheral #{} becomes ready for data exchange.", address);
(
PeripheralState::PreDataExchange,
Some(PeripheralEvent::Configured),
)
} else {
(PeripheralState::ValidateConfig, None)
}
} else {
(PeripheralState::ValidateConfig, None)
};
self.state = new_state;
event
}
PeripheralState::DataExchange | PeripheralState::PreDataExchange => {
if self.diag_needed {
if self.handle_diagnostics_response(fdl, &telegram).is_some() {
self.retry_count = 0;
self.diag_needed = false;
Some(PeripheralEvent::Diagnostics)
} else {
None
}
} else {
let event = match telegram {
crate::fdl::Telegram::Data(t) => {
let data_ok = match t.is_response().unwrap() {
crate::fdl::ResponseStatus::SapNotEnabled => {
log::warn!(
"Got \"SAP not enabled\" response from #{}, revalidating config...",
self.address
);
self.state = PeripheralState::ValidateConfig;
false
}
crate::fdl::ResponseStatus::Ok => true, crate::fdl::ResponseStatus::DataLow => true,
crate::fdl::ResponseStatus::DataHigh => {
log::debug!(
"Peripheral #{} signals diagnostics!",
self.address
);
self.diag_needed = true;
true
}
e => {
log::warn!(
"Unhandled response status \"{:?}\" from #{}!",
e,
self.address
);
false
}
};
if data_ok {
if t.pdu.len() == self.pi_i.len() {
self.pi_i.copy_from_slice(&t.pdu);
self.state = PeripheralState::DataExchange;
Some(PeripheralEvent::DataExchanged)
} else {
log::warn!(
"Got response from #{} with unexpected PDU length (got: {}, want: {})!",
self.address,
t.pdu.len(),
self.pi_i.len()
);
None
}
} else {
None
}
}
crate::fdl::Telegram::ShortConfirmation(_) => {
if self.pi_i.len() != 0 {
log::warn!(
"#{} responded with SC but we expected cyclic data?!",
self.address
);
None
} else {
self.state = PeripheralState::DataExchange;
Some(PeripheralEvent::DataExchanged)
}
}
crate::fdl::Telegram::Token(_) => unreachable!(),
};
#[cfg(feature = "debug-measure-roundtrip")]
if let Some(tx_time) = self.tx_time {
log::debug!(
"Data-Exchange Roundtrip Time for #{}: {} us",
self.address,
(now - tx_time).total_micros()
);
}
self.retry_count = 0;
self.fcb.cycle();
event
}
}
}
}
fn send_diagnostics_request(
&mut self,
master: &crate::fdl::FdlActiveStation,
tx: crate::fdl::TelegramTx,
) -> crate::fdl::TelegramTxResponse {
tx.send_data_telegram(
crate::fdl::DataTelegramHeader {
da: self.address,
sa: master.parameters().address,
dsap: crate::consts::SAP_SLAVE_DIAGNOSIS,
ssap: crate::consts::SAP_MASTER_MS0,
fc: crate::fdl::FunctionCode::new_srd_low(self.fcb),
},
0,
|_buf| (),
)
}
fn handle_diagnostics_response(
&mut self,
master: &crate::fdl::FdlActiveStation,
telegram: &crate::fdl::Telegram,
) -> Option<&DiagnosticsInfo> {
if let crate::fdl::Telegram::Data(t) = telegram {
if t.h.dsap != crate::consts::SAP_MASTER_MS0 {
log::warn!(
"Diagnostics response by #{} to wrong SAP: {t:?}",
self.address
);
return None;
}
if t.h.ssap != crate::consts::SAP_SLAVE_DIAGNOSIS {
log::warn!(
"Diagnostics response by #{} from wrong SAP: {t:?}",
self.address
);
return None;
}
if t.pdu.len() < 6 {
log::warn!(
"Diagnostics response by #{} is too short: {t:?}",
self.address
);
return None;
}
let master_address = if t.pdu[3] == 255 {
None
} else {
Some(t.pdu[3])
};
let mut diag = DiagnosticsInfo {
flags: DiagnosticFlags::from_bits_retain(u16::from_le_bytes(
t.pdu[0..2].try_into().unwrap(),
)),
master_address,
ident_number: u16::from_be_bytes(t.pdu[4..6].try_into().unwrap()),
};
if !diag.flags.contains(DiagnosticFlags::PERMANENT_BIT) {
log::warn!("Inconsistent diagnostics for peripheral #{}!", self.address);
}
diag.flags.remove(DiagnosticFlags::PERMANENT_BIT);
log::debug!("Peripheral Diagnostics (#{}): {:?}", self.address, diag);
if diag.flags.contains(DiagnosticFlags::EXT_DIAG) {
if self.ext_diag.fill(&t.pdu[6..]) {
log::debug!(
"Extended Diagnostics (#{}): {:?}",
self.address,
self.ext_diag
);
}
}
self.fcb.cycle();
self.diag = Some(diag);
self.diag.as_ref()
} else {
log::warn!(
"Unexpected diagnostics response for #{}: {telegram:?}",
self.address
);
None
}
}
}