#![deny(unused_must_use)]
use crate::fdl::FdlApplication;
use crate::phy::ProfibusPhy;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
pub enum ConnectivityState {
Offline,
Passive,
Online,
}
impl ConnectivityState {
#[inline(always)]
pub fn is_offline(self) -> bool {
self == ConnectivityState::Offline
}
#[inline(always)]
pub fn is_passive(self) -> bool {
self == ConnectivityState::Passive
}
#[inline(always)]
pub fn is_online(self) -> bool {
self == ConnectivityState::Online
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum GapState {
Waiting { rotation_count: u8 },
DoPoll { current_address: crate::Address },
}
impl GapState {
pub fn increment_wait(&mut self) {
match self {
GapState::Waiting {
ref mut rotation_count,
} => *rotation_count += 1,
_ => (),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum PassTokenAttempt {
First,
Second,
Third,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum ScheduleNext {
Scheduled,
CycleCompleted,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct UseTokenData {
pub token_time: crate::time::Instant,
pub first_app: Option<usize>,
}
impl UseTokenData {
pub fn with_token_time(token_time: crate::time::Instant) -> Self {
Self {
token_time,
first_app: None,
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum State {
Offline,
PassiveIdle,
ListenToken {
status_request: Option<crate::Address>,
collision_count: u8,
},
ActiveIdle {
status_request: Option<crate::Address>,
new_previous_station: Option<crate::Address>,
collision_count: u8,
},
UseToken {
data: UseTokenData,
first_cycle_done: bool,
},
ClaimToken {
first: bool,
},
AwaitDataResponse {
address: crate::Address,
data: UseTokenData,
},
PassToken {
do_gap: bool,
attempt: PassTokenAttempt,
},
CheckTokenPass {
attempt: PassTokenAttempt,
},
AwaitStatusResponse {
address: crate::Address,
},
}
macro_rules! debug_assert_state {
($state:expr, $expected:pat) => {
debug_assert!(
matches!($state, $expected),
"unexpected state: {:?}",
$state
)
};
}
impl State {
pub fn have_token(&self) -> bool {
match self {
State::Offline { .. }
| State::PassiveIdle { .. }
| State::ListenToken { .. }
| State::ActiveIdle { .. }
| State::PassToken { .. }
| State::CheckTokenPass { .. } => false,
State::ClaimToken { .. }
| State::UseToken { .. }
| State::AwaitDataResponse { .. }
| State::AwaitStatusResponse { .. } => true,
}
}
fn transition_offline(&mut self) {
debug_assert_state!(
self,
State::Offline { .. }
| State::PassiveIdle { .. }
| State::ListenToken { .. }
| State::PassToken { .. }
);
*self = State::Offline
}
fn transition_passive_idle(&mut self) {
debug_assert_state!(self, State::Offline { .. } | State::PassiveIdle { .. });
*self = State::PassiveIdle;
}
fn transition_listen_token(&mut self) {
debug_assert_state!(
self,
State::ListenToken { .. } | State::Offline { .. } | State::ActiveIdle { .. }
);
*self = State::ListenToken {
status_request: None,
collision_count: 0,
};
}
fn transition_active_idle(&mut self) {
debug_assert_state!(
self,
State::ActiveIdle { .. }
| State::ListenToken { .. }
| State::UseToken { .. }
| State::AwaitDataResponse { .. }
| State::CheckTokenPass { .. }
| State::AwaitStatusResponse { .. }
);
*self = State::ActiveIdle {
status_request: None,
new_previous_station: None,
collision_count: 0,
};
}
fn transition_use_token(&mut self, data: UseTokenData) {
debug_assert_state!(
self,
State::UseToken { .. }
| State::ClaimToken { .. }
| State::PassToken { .. }
| State::AwaitDataResponse { .. }
| State::ActiveIdle { .. }
);
*self = State::UseToken {
data,
first_cycle_done: false,
};
}
fn transition_claim_token(&mut self) {
debug_assert_state!(
self,
State::ClaimToken { .. } | State::ListenToken { .. } | State::ActiveIdle { .. }
);
*self = State::ClaimToken { first: true };
}
fn transition_await_data_response(&mut self, address: crate::Address, data: UseTokenData) {
debug_assert_state!(
self,
State::AwaitDataResponse { .. } | State::UseToken { .. }
);
*self = State::AwaitDataResponse { address, data };
}
fn transition_pass_token(&mut self, do_gap: bool, attempt: PassTokenAttempt) {
debug_assert_state!(
self,
State::PassToken { .. }
| State::UseToken { .. }
| State::ClaimToken { .. }
| State::CheckTokenPass { .. }
| State::AwaitStatusResponse { .. }
);
*self = State::PassToken { do_gap, attempt };
}
fn transition_check_token_pass(&mut self, attempt: PassTokenAttempt) {
debug_assert_state!(self, State::CheckTokenPass { .. } | State::PassToken { .. });
*self = State::CheckTokenPass { attempt };
}
fn transition_await_status_response(&mut self, address: crate::Address) {
debug_assert_state!(
self,
State::AwaitStatusResponse { .. } | State::PassToken { .. }
);
*self = State::AwaitStatusResponse { address };
}
}
impl State {
fn get_listen_token_status_request(&mut self) -> &mut Option<crate::Address> {
match self {
Self::ListenToken { status_request, .. } => status_request,
_ => unreachable!(),
}
}
fn get_listen_token_collision_count(&mut self) -> &mut u8 {
match self {
Self::ListenToken {
collision_count, ..
} => collision_count,
_ => unreachable!(),
}
}
fn get_active_idle_status_request(&mut self) -> &mut Option<crate::Address> {
match self {
Self::ActiveIdle { status_request, .. } => status_request,
_ => unreachable!(),
}
}
fn get_active_idle_new_previous_station(&mut self) -> &mut Option<crate::Address> {
match self {
Self::ActiveIdle {
new_previous_station,
..
} => new_previous_station,
_ => unreachable!(),
}
}
fn get_active_idle_collision_count(&mut self) -> &mut u8 {
match self {
Self::ActiveIdle {
collision_count, ..
} => collision_count,
_ => unreachable!(),
}
}
fn get_use_token_data(&mut self) -> &mut UseTokenData {
match self {
Self::UseToken { data, .. } => data,
_ => unreachable!(),
}
}
fn get_use_token_first_cycle_done(&mut self) -> &mut bool {
match self {
Self::UseToken {
first_cycle_done, ..
} => first_cycle_done,
_ => unreachable!(),
}
}
fn get_claim_token_first(&mut self) -> &mut bool {
match self {
Self::ClaimToken { first, .. } => first,
_ => unreachable!(),
}
}
fn get_await_data_response_address(&mut self) -> &mut crate::Address {
match self {
Self::AwaitDataResponse { address, .. } => address,
_ => unreachable!(),
}
}
fn get_await_data_response_data(&mut self) -> &mut UseTokenData {
match self {
Self::AwaitDataResponse { data, .. } => data,
_ => unreachable!(),
}
}
fn get_pass_token_do_gap(&mut self) -> &mut bool {
match self {
Self::PassToken { do_gap, .. } => do_gap,
_ => unreachable!(),
}
}
fn get_pass_token_attempt(&mut self) -> &mut PassTokenAttempt {
match self {
Self::PassToken { attempt, .. } => attempt,
_ => unreachable!(),
}
}
fn get_await_status_response_address(&mut self) -> &mut crate::Address {
match self {
Self::AwaitStatusResponse { address, .. } => address,
_ => unreachable!(),
}
}
fn get_check_token_pass_attempt(&mut self) -> &mut PassTokenAttempt {
match self {
Self::CheckTokenPass { attempt, .. } => attempt,
_ => unreachable!(),
}
}
}
#[derive(Debug)]
pub struct FdlActiveStation {
p: crate::fdl::Parameters,
token_ring: crate::fdl::TokenRing,
connectivity_state: ConnectivityState,
gap_state: GapState,
state: State,
last_bus_activity: Option<crate::time::Instant>,
pending_bytes: usize,
last_token_time: crate::time::Instant,
end_token_hold_time: crate::time::Instant,
next_application: usize,
}
impl FdlActiveStation {
pub fn new(param: crate::fdl::Parameters) -> Self {
param.debug_assert_consistency();
Self {
token_ring: crate::fdl::TokenRing::new(¶m),
connectivity_state: ConnectivityState::Offline,
gap_state: GapState::DoPoll {
current_address: param.address,
},
state: State::Offline,
last_bus_activity: None,
pending_bytes: 0,
last_token_time: crate::time::Instant::ZERO,
end_token_hold_time: crate::time::Instant::ZERO,
next_application: 0,
p: param,
}
}
#[inline(always)]
pub fn parameters(&self) -> &crate::fdl::Parameters {
&self.p
}
#[inline(always)]
pub fn connectivity_state(&self) -> ConnectivityState {
self.connectivity_state
}
#[inline]
pub fn set_state(&mut self, state: ConnectivityState) {
log::info!("FDL active station entering state \"{:?}\"", state);
self.connectivity_state = state;
if state == ConnectivityState::Offline {
let parameters = core::mem::take(&mut self.p);
*self = Self::new(parameters);
} else if state != ConnectivityState::Online {
todo!(
"ConnectivityState {:?} is not yet supported properly!",
state
);
}
}
#[inline]
pub fn set_offline(&mut self) {
self.set_state(ConnectivityState::Offline)
}
#[inline]
pub fn set_passive(&mut self) {
self.set_state(ConnectivityState::Passive)
}
#[inline]
pub fn set_online(&mut self) {
self.set_state(ConnectivityState::Online)
}
pub fn is_in_ring(&self) -> bool {
matches!(
self.state,
State::UseToken { .. }
| State::PassToken { .. }
| State::ActiveIdle { .. }
| State::ClaimToken { .. }
| State::CheckTokenPass { .. }
| State::AwaitDataResponse { .. }
| State::AwaitStatusResponse { .. }
)
}
#[doc(hidden)]
pub fn inspect_token_ring(&self) -> &crate::fdl::TokenRing {
&self.token_ring
}
}
#[must_use = "\"poll done\" marker must lead to exit of poll function!"]
struct PollDone();
#[must_use = "\"poll result\" must lead to exit of poll function!"]
struct PollResult<E> {
events: E,
}
impl PollDone {
pub fn waiting_for_transmission() -> Self {
PollDone()
}
pub fn waiting_for_bus() -> Self {
PollDone()
}
pub fn waiting_for_delay() -> Self {
PollDone()
}
pub fn offline() -> Self {
PollDone()
}
pub fn with_events<E>(self, events: E) -> PollResult<E> {
PollResult { events }
}
}
impl<E: Default> From<PollDone> for PollResult<E> {
fn from(value: PollDone) -> Self {
PollResult {
events: Default::default(),
}
}
}
macro_rules! return_if_done {
($expr:expr) => {
match $expr {
Some(e) => return e.into(),
None => (),
}
};
}
impl FdlActiveStation {
fn mark_bus_activity(&mut self, now: crate::time::Instant) {
let last = self.last_bus_activity.get_or_insert(now);
*last = (*last).max(now);
}
fn check_for_ongoing_transmision(
&mut self,
now: crate::time::Instant,
phy: &mut impl ProfibusPhy,
) -> Option<PollDone> {
let phy_transmitting = phy.poll_transmission(now);
if phy_transmitting || self.last_bus_activity.map(|l| now <= l).unwrap_or(false) {
self.mark_bus_activity(now);
Some(PollDone::waiting_for_transmission())
} else {
None
}
}
fn wait_synchronization_pause(&mut self, now: crate::time::Instant) -> Option<PollDone> {
if now <= (*self.last_bus_activity.get_or_insert(now) + self.p.bits_to_time(33)) {
Some(PollDone::waiting_for_delay())
} else {
None
}
}
fn mark_tx(&mut self, now: crate::time::Instant, bytes: usize) -> PollDone {
self.last_bus_activity = Some(
now + self
.p
.baudrate
.bits_to_time(11 * u32::try_from(bytes).unwrap()),
);
PollDone::waiting_for_transmission()
}
fn check_for_bus_activity(&mut self, now: crate::time::Instant, phy: &mut impl ProfibusPhy) {
let pending_bytes = phy.poll_pending_received_bytes(now);
if pending_bytes > self.pending_bytes {
self.mark_bus_activity(now);
self.pending_bytes = pending_bytes;
}
}
fn mark_rx(&mut self, now: crate::time::Instant) {
self.pending_bytes = 0;
self.mark_bus_activity(now);
}
fn check_slot_expired(&mut self, now: crate::time::Instant) -> bool {
let last_bus_activity = *self.last_bus_activity.get_or_insert(now);
if self.pending_bytes == 0 {
now > (last_bus_activity + self.p.slot_time())
} else {
now > (last_bus_activity + self.p.slot_time())
}
}
}
impl FdlActiveStation {
#[must_use = "poll done marker"]
fn handle_lost_token(
&mut self,
now: crate::time::Instant,
phy: &mut impl ProfibusPhy,
) -> Option<PollDone> {
let last_bus_activity = *self.last_bus_activity.get_or_insert(now);
if (now - last_bus_activity) >= self.p.token_lost_timeout() {
if self.token_ring.ready_for_ring() {
log::warn!("Token lost! Generating a new one.");
} else {
log::info!("Generating new token due to silent bus.");
}
self.state.transition_claim_token();
Some(self.do_claim_token(now, phy))
} else {
None
}
}
fn next_gap_poll(&self, current_address: crate::Address) -> GapState {
let next_station = self.token_ring.next_station();
let next_address = if current_address == (self.p.highest_station_address - 1) {
0
} else {
current_address + 1
};
if next_address >= next_station && next_station > self.p.address {
GapState::Waiting { rotation_count: 0 }
} else if next_address >= next_station
&& next_station < self.p.address
&& next_address < self.p.address
{
GapState::Waiting { rotation_count: 0 }
} else {
GapState::DoPoll {
current_address: next_address,
}
}
}
}
impl FdlActiveStation {
#[must_use = "poll done marker"]
fn do_listen_token<'a, PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
) -> PollDone {
debug_assert_state!(self.state, State::ListenToken { .. });
return_if_done!(self.handle_lost_token(now, phy));
if let Some(status_request_source) = *self.state.get_listen_token_status_request() {
return_if_done!(self.wait_synchronization_pause(now));
let state = if self.token_ring.ready_for_ring()
&& status_request_source == self.token_ring.previous_station()
{
crate::fdl::ResponseState::MasterWithoutToken
} else {
crate::fdl::ResponseState::MasterNotReady
};
let tx_res = phy
.transmit_telegram(now, |tx| {
Some(tx.send_fdl_status_response(
status_request_source,
self.p.address,
state,
crate::fdl::ResponseStatus::Ok,
))
})
.unwrap();
if self.token_ring.ready_for_ring() {
self.state.transition_active_idle();
} else {
*self.state.get_listen_token_status_request() = None;
}
return self.mark_tx(now, tx_res.bytes_sent());
}
phy.receive_all_telegrams(now, |telegram, is_last_telegram| {
self.mark_rx(now);
if self.connectivity_state() == ConnectivityState::Offline {
return PollDone::waiting_for_bus();
}
if telegram.source_address() == Some(self.p.address) {
let collision_count = self.state.get_listen_token_collision_count();
*collision_count += 1;
match *collision_count {
1 => {
log::warn!("Witnessed collision of another active station with own address (#{})!", self.p.address);
}
2 | _ => {
log::warn!(
"Witnessed second collision of another active station with own address (#{}), going offline.",
self.p.address,
);
self.set_offline();
}
}
return PollDone::waiting_for_bus();
}
match telegram {
crate::fdl::Telegram::Token(token_telegram) => {
self.token_ring.witness_token_pass(token_telegram.sa, token_telegram.da);
PollDone::waiting_for_bus()
}
crate::fdl::Telegram::Data(data_telegram)
if data_telegram.is_fdl_status_request().is_some()
&& data_telegram.h.da == self.p.address =>
{
if is_last_telegram {
*self.state.get_listen_token_status_request() = Some(data_telegram.h.sa);
PollDone::waiting_for_delay()
} else {
PollDone::waiting_for_bus()
}
}
_ => PollDone::waiting_for_bus(),
}
}).unwrap_or(PollDone::waiting_for_bus())
}
fn handle_telegram(
&mut self,
now: crate::time::Instant,
telegram: crate::fdl::Telegram,
is_last_telegram: bool,
) -> PollDone {
if matches!(self.state, State::ListenToken { .. }) {
return PollDone::waiting_for_bus();
}
debug_assert_state!(self.state, State::ActiveIdle { .. });
match telegram {
crate::fdl::Telegram::Token(token_telegram) => {
let collision_count = self.state.get_active_idle_collision_count();
if token_telegram.sa == self.p.address {
*collision_count += 1;
match *collision_count {
1 => {
log::warn!("Witnessed collision of another active station with own address (#{})!", self.p.address);
}
2 | _ => {
log::warn!(
"Witnessed second collision of another active station with own address (#{}), leaving ring.",
self.p.address,
);
self.state.transition_listen_token();
}
}
return PollDone::waiting_for_bus();
} else {
*collision_count = 0;
}
if token_telegram.da != self.p.address || !is_last_telegram {
self.token_ring
.witness_token_pass(token_telegram.sa, token_telegram.da);
PollDone::waiting_for_bus()
} else {
if token_telegram.sa == self.token_ring.previous_station() {
self.state
.transition_use_token(UseTokenData::with_token_time(now));
PollDone::waiting_for_delay()
} else {
match *self.state.get_active_idle_new_previous_station() {
Some(address) if address == token_telegram.sa => {
self.token_ring
.witness_token_pass(token_telegram.sa, token_telegram.da);
self.state
.transition_use_token(UseTokenData::with_token_time(now));
PollDone::waiting_for_delay()
}
_ => {
*self.state.get_active_idle_new_previous_station() =
Some(token_telegram.sa);
PollDone::waiting_for_bus()
}
}
}
}
}
crate::fdl::Telegram::Data(data_telegram)
if data_telegram.is_fdl_status_request().is_some()
&& data_telegram.h.da == self.p.address
&& is_last_telegram =>
{
*self.state.get_active_idle_status_request() = Some(data_telegram.h.sa);
PollDone::waiting_for_delay()
}
_ => PollDone::waiting_for_bus(),
}
}
#[must_use = "poll done marker"]
fn do_active_idle<'a, PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
) -> PollDone {
debug_assert_state!(self.state, State::ActiveIdle { .. });
return_if_done!(self.handle_lost_token(now, phy));
if let Some(status_request_source) = *self.state.get_active_idle_status_request() {
return_if_done!(self.wait_synchronization_pause(now));
let tx_res = phy
.transmit_telegram(now, |tx| {
Some(tx.send_fdl_status_response(
status_request_source,
self.p.address,
crate::fdl::ResponseState::MasterInRing,
crate::fdl::ResponseStatus::Ok,
))
})
.unwrap();
*self.state.get_active_idle_status_request() = None;
return self.mark_tx(now, tx_res.bytes_sent());
}
phy.receive_all_telegrams(now, |telegram, is_last_telegram| {
self.mark_rx(now);
self.handle_telegram(now, telegram, is_last_telegram)
})
.unwrap_or(PollDone::waiting_for_bus())
}
#[must_use = "poll done marker"]
fn do_claim_token<'a, PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
) -> PollDone {
debug_assert_state!(self.state, State::ClaimToken { .. });
return_if_done!(self.wait_synchronization_pause(now));
let tx_res = phy
.transmit_telegram(now, |tx| {
Some(tx.send_token_telegram(self.p.address, self.p.address))
})
.unwrap();
self.token_ring.claim_token();
if *self.state.get_claim_token_first() {
*self.state.get_claim_token_first() = false;
} else {
self.state
.transition_use_token(UseTokenData::with_token_time(now));
}
self.mark_tx(now, tx_res.bytes_sent())
}
#[must_use = "poll done marker"]
fn app_transmit_telegram<PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
app: &mut dyn FdlApplication,
high_prio_only: bool,
) -> Option<PollDone> {
if let Some(tx_res) = phy.transmit_telegram(now, |tx| {
let res = app.transmit_telegram(now, self, tx, high_prio_only);
res
}) {
if let Some(addr) = tx_res.expects_reply() {
let data = *self.state.get_use_token_data();
self.state.transition_await_data_response(addr, data);
}
Some(self.mark_tx(now, tx_res.bytes_sent()))
} else {
None
}
}
fn schedule_next_application(&mut self, num_apps: usize) -> ScheduleNext {
let data = self.state.get_use_token_data();
let first_app = *data.first_app.get_or_insert(self.next_application);
self.next_application = (self.next_application + 1) % num_apps;
if self.next_application == first_app {
ScheduleNext::CycleCompleted
} else {
ScheduleNext::Scheduled
}
}
#[must_use = "poll done marker"]
fn apps_transmit_telegram<PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
apps: &mut [&mut dyn FdlApplication],
high_prio_only: bool,
) -> Option<PollDone> {
for _ in 0..apps.len() {
let current_app = &mut apps[self.next_application];
let res = self.app_transmit_telegram(now, phy, *current_app, high_prio_only);
return_if_done!(res);
if self.schedule_next_application(apps.len()) == ScheduleNext::CycleCompleted {
break;
}
}
None
}
#[must_use = "poll done marker"]
fn do_use_token<PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
apps: &mut [&mut dyn FdlApplication],
) -> PollDone {
debug_assert_state!(self.state, State::UseToken { .. });
let data = *self.state.get_use_token_data();
if self.last_token_time != data.token_time {
self.end_token_hold_time = self.last_token_time + self.p.token_rotation_time();
self.last_token_time = data.token_time;
if let GapState::DoPoll { .. } = self.gap_state {
self.end_token_hold_time -= self.p.bits_to_time(u32::from(self.p.slot_bits) + 100);
}
}
return_if_done!(self.wait_synchronization_pause(now));
if now < self.end_token_hold_time {
*self.state.get_use_token_first_cycle_done() = true;
return_if_done!(self.apps_transmit_telegram(now, phy, apps, false));
} else if !*self.state.get_use_token_first_cycle_done() {
*self.state.get_use_token_first_cycle_done() = true;
return_if_done!(self.apps_transmit_telegram(now, phy, apps, true));
}
self.state
.transition_pass_token(true, PassTokenAttempt::First);
PollDone::waiting_for_delay()
}
fn do_await_data_response<PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
apps: &mut [&mut dyn FdlApplication],
) -> PollDone {
debug_assert_state!(self.state, State::AwaitDataResponse { .. });
let address = *self.state.get_await_data_response_address();
let data = *self.state.get_await_data_response_data();
let app = &mut apps[self.next_application];
let reply_events: Result<Option<()>, PollDone> = phy
.receive_telegram(now, |telegram| {
self.mark_rx(now);
let is_valid_response = match &telegram {
crate::fdl::Telegram::Token(_) => false,
crate::fdl::Telegram::ShortConfirmation(_) => true,
crate::fdl::Telegram::Data(t) => {
t.h.sa == address && t.h.da == self.p.address && matches!(t.h.fc, crate::fdl::FunctionCode::Response { .. })
},
};
if is_valid_response {
Ok(Some(app.receive_reply(now, self, address, telegram)))
} else {
log::warn!("Received unexpected telegram while waiting for reply from #{address}: {:?}", telegram);
self.state.transition_active_idle();
Err(PollDone::waiting_for_bus())
}
})
.unwrap_or(Ok(None));
match reply_events {
Err(d) => {
return d.into();
}
Ok(Some(())) => {
self.state.transition_use_token(data);
*self.state.get_use_token_first_cycle_done() = true;
return PollDone::waiting_for_delay();
}
Ok(None) => (),
}
if self.check_slot_expired(now) {
app.handle_timeout(now, self, address);
self.state.transition_use_token(data);
*self.state.get_use_token_first_cycle_done() = true;
self.do_use_token(now, phy, apps)
} else {
PollDone::waiting_for_bus()
}
}
#[must_use = "poll done marker"]
fn do_pass_token<'a, PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
) -> PollDone {
debug_assert_state!(self.state, State::PassToken { .. });
return_if_done!(self.wait_synchronization_pause(now));
if *self.state.get_pass_token_do_gap() {
match &mut self.gap_state {
GapState::Waiting {
ref mut rotation_count,
} => {
if *rotation_count > self.p.gap_wait_rotations {
log::debug!("Starting next gap polling cycle!");
self.gap_state = self.next_gap_poll(self.p.address);
} else {
*rotation_count += 1;
}
}
GapState::DoPoll { current_address } => {
let current_address = *current_address;
self.gap_state = self.next_gap_poll(current_address);
}
}
if let GapState::DoPoll { current_address } = self.gap_state {
let tx_res = phy
.transmit_telegram(now, |tx| {
Some(tx.send_fdl_status_request(current_address, self.p.address))
})
.unwrap();
self.state.transition_await_status_response(current_address);
return self.mark_tx(now, tx_res.bytes_sent());
}
}
let tx_res = phy
.transmit_telegram(now, |tx| {
Some(tx.send_token_telegram(self.token_ring.next_station(), self.p.address))
})
.unwrap();
self.token_ring
.witness_token_pass(self.p.address, self.token_ring.next_station());
if self.token_ring.next_station() == self.p.address {
self.state
.transition_use_token(UseTokenData::with_token_time(now));
} else {
let attempt = *self.state.get_pass_token_attempt();
self.state.transition_check_token_pass(attempt);
}
self.mark_tx(now, tx_res.bytes_sent())
}
#[must_use = "poll done marker"]
fn do_await_status_response<'a, PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
) -> PollDone {
debug_assert_state!(self.state, State::AwaitStatusResponse { .. });
let address = *self.state.get_await_status_response_address();
let received = phy.receive_telegram(now, |telegram| {
self.mark_rx(now);
if let crate::fdl::Telegram::Data(telegram) = &telegram {
if telegram.h.sa == address && telegram.h.da == self.p.address {
if let crate::fdl::FunctionCode::Response { state, status } = telegram.h.fc {
log::trace!("Address #{address} responded");
if status == crate::fdl::ResponseStatus::Ok
&& matches!(state, crate::fdl::ResponseState::MasterWithoutToken | crate::fdl::ResponseState::MasterInRing) {
self.token_ring.set_next_station(address);
}
self.state.transition_pass_token(false, PassTokenAttempt::First);
return PollDone::waiting_for_delay();
}
}
}
log::warn!("Received unexpected telegram while waiting for status reply from #{address}: {telegram:?}");
self.state.transition_active_idle();
PollDone::waiting_for_bus()
});
if let Some(res) = received {
return res;
}
if self.check_slot_expired(now) {
log::trace!("No reply from #{address}");
self.state
.transition_pass_token(false, PassTokenAttempt::First);
self.do_pass_token(now, phy)
} else {
PollDone::waiting_for_bus()
}
}
#[must_use = "poll done marker"]
fn do_check_token_pass<'a, PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
) -> PollDone {
debug_assert_state!(self.state, State::CheckTokenPass { .. });
if self.check_slot_expired(now) {
match *self.state.get_check_token_pass_attempt() {
PassTokenAttempt::First => {
log::warn!(
"Token was apparently not received by #{}, resending...",
self.token_ring.next_station()
);
self.state
.transition_pass_token(false, PassTokenAttempt::Second);
}
PassTokenAttempt::Second => {
log::warn!(
"Token was again not received by #{}, resending...",
self.token_ring.next_station()
);
self.state
.transition_pass_token(false, PassTokenAttempt::Third);
}
PassTokenAttempt::Third => {
log::warn!(
"Token was also not received on third attempt, clearing #{} from LAS.",
self.token_ring.next_station()
);
self.token_ring
.remove_station(self.token_ring.next_station());
self.state
.transition_pass_token(false, PassTokenAttempt::First);
}
}
return self.do_pass_token(now, phy);
}
let mut first_in = true;
phy.receive_all_telegrams(now, |telegram, is_last_telegram| {
self.mark_rx(now);
if first_in {
if telegram.source_address() != Some(self.token_ring.next_station()) {
log::warn!(
"Unexpected station #{} transmitting after token pass to #{}",
telegram.source_address().unwrap(),
self.token_ring.next_station()
);
}
self.state.transition_active_idle();
first_in = false;
}
self.handle_telegram(now, telegram, is_last_telegram)
})
.unwrap_or(PollDone::waiting_for_bus())
}
pub fn poll<PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
app: &mut dyn FdlApplication,
) {
let _result = self.poll_inner(now, phy, &mut [app]);
}
pub fn poll_multi<PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
apps: &mut [&mut dyn FdlApplication],
) {
let _result = self.poll_inner(now, phy, apps);
}
fn poll_inner<PHY: ProfibusPhy>(
&mut self,
now: crate::time::Instant,
phy: &mut PHY,
apps: &mut [&mut dyn FdlApplication],
) -> PollDone {
match self.connectivity_state {
ConnectivityState::Offline => {
debug_assert!(matches!(self.state, State::Offline));
return PollDone::offline().into();
}
ConnectivityState::Passive => {
match &self.state {
State::ActiveIdle { .. } | State::ListenToken { .. } | State::Offline => {
self.state.transition_passive_idle();
}
State::PassiveIdle => (),
s => {
log::debug!("Can't transition from \"{s:?}\" to PassiveIdle");
}
}
}
ConnectivityState::Online => {
if matches!(self.state, State::Offline | State::PassiveIdle) {
self.state.transition_listen_token();
}
}
}
return_if_done!(self.check_for_ongoing_transmision(now, phy));
self.check_for_bus_activity(now, phy);
match &self.state {
State::Offline { .. } => unreachable!(),
State::ListenToken { .. } => self.do_listen_token(now, phy).into(),
State::ClaimToken { .. } => self.do_claim_token(now, phy).into(),
State::UseToken { .. } => self.do_use_token(now, phy, apps).into(),
State::AwaitDataResponse { .. } => self.do_await_data_response(now, phy, apps).into(),
State::PassToken { .. } => self.do_pass_token(now, phy).into(),
State::CheckTokenPass { .. } => self.do_check_token_pass(now, phy).into(),
State::ActiveIdle { .. } => self.do_active_idle(now, phy).into(),
State::AwaitStatusResponse { .. } => self.do_await_status_response(now, phy).into(),
s => todo!("Active station state {s:?} not implemented yet!"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fdl_active_station_struct_size() {
let size = std::mem::size_of::<FdlActiveStation>();
println!("FDL active station struct is {size} bytes large.");
assert!(size <= 256);
}
#[test]
fn fdl_active_station_smoke() {
crate::test_utils::prepare_test_logger();
let mut phy = crate::phy::SimulatorPhy::new(crate::Baudrate::B19200, "phy");
let mut fdl = FdlActiveStation::new(Default::default());
crate::test_utils::set_active_addr(fdl.parameters().address);
fdl.set_online();
fdl.set_offline();
fdl.set_online();
let mut now = crate::time::Instant::ZERO;
while now.total_millis() < 200 {
fdl.poll(now, &mut phy, &mut ());
now += crate::time::Duration::from_micros(100);
phy.set_bus_time(now);
crate::test_utils::set_log_timestamp(now);
}
}
}