use super::{Instant, Millis};
pub mod iso2 {
use super::Millis;
pub const SECC_SEQUENCE_TIMEOUT: Millis = Millis::from_secs(60);
pub const EVCC_SEQUENCE_PERFORMANCE_TIME: Millis = Millis::from_secs(40);
pub const EVCC_COMMUNICATION_SETUP_TIMEOUT: Millis = Millis::from_secs(20);
pub const SECC_COMMUNICATION_SETUP_PERFORMANCE_TIME: Millis = Millis::from_secs(18);
pub const EVCC_ONGOING_TIMEOUT: Millis = Millis::from_secs(60);
pub const SECC_ONGOING_PERFORMANCE_TIME: Millis = Millis::from_secs(55);
pub const EVCC_CABLE_CHECK_TIMEOUT: Millis = Millis::from_secs(40);
pub const SECC_CABLE_CHECK_PERFORMANCE_TIME: Millis = Millis::from_secs(38);
pub const EVCC_PRE_CHARGE_TIMEOUT: Millis = Millis::from_secs(7);
pub const SECC_PRE_CHARGE_PERFORMANCE_TIME: Millis = Millis::from_secs(5);
pub const MSG_TIMEOUT_DEFAULT: Millis = Millis::from_secs(2);
pub const MSG_TIMEOUT_BACKEND: Millis = Millis::from_secs(5);
pub const MSG_TIMEOUT_CURRENT_DEMAND: Millis = Millis::from_millis(250);
pub const SECC_MSG_PERFORMANCE_DEFAULT: Millis = Millis::from_millis(1_500);
pub const SECC_MSG_PERFORMANCE_BACKEND: Millis = Millis::from_millis(4_500);
pub const SECC_MSG_PERFORMANCE_CURRENT_DEMAND: Millis = Millis::from_millis(25);
}
pub mod iso20 {
use super::Millis;
pub const SECC_SEQUENCE_TIMEOUT: Millis = Millis::from_secs(60);
pub const EVCC_COMMUNICATION_SETUP_TIMEOUT: Millis = Millis::from_secs(20);
pub const ONGOING_TIMEOUT: Millis = Millis::from_secs(60);
pub const SECC_ONGOING_PERFORMANCE_TIME: Millis = Millis::from_secs(55);
pub const EIM_ONGOING_TIMEOUT: Millis = Millis::from_secs(180);
pub const SECC_EIM_ONGOING_PERFORMANCE_TIME: Millis = Millis::from_secs(175);
pub const EVCC_SEQUENCE_PERFORMANCE_TIME: Millis = Millis::from_secs(40);
}
pub mod sdp {
use super::Millis;
pub const RESPONSE_TIMEOUT: Millis = Millis::from_millis(250);
pub const MAX_REQUESTS: u32 = 50;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Timer {
Sequence,
Message,
Ongoing,
CommunicationSetup,
}
const TIMER_COUNT: usize = 4;
impl Timer {
const ALL: [Self; TIMER_COUNT] =
[Self::Sequence, Self::Message, Self::Ongoing, Self::CommunicationSetup];
const fn index(self) -> usize {
match self {
Self::Sequence => 0,
Self::Message => 1,
Self::Ongoing => 2,
Self::CommunicationSetup => 3,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Sequence => "sequence",
Self::Message => "message",
Self::Ongoing => "ongoing",
Self::CommunicationSetup => "communication-setup",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Timers {
deadlines: [Option<Instant>; TIMER_COUNT],
}
impl Timers {
#[must_use]
pub const fn new() -> Self {
Self { deadlines: [None; TIMER_COUNT] }
}
pub const fn arm(&mut self, timer: Timer, now: Instant, after: Millis) {
self.deadlines[timer.index()] = Some(now.saturating_add(after));
}
pub const fn disarm(&mut self, timer: Timer) {
self.deadlines[timer.index()] = None;
}
pub fn disarm_all(&mut self) {
self.deadlines = [None; TIMER_COUNT];
}
#[must_use]
pub const fn deadline(&self, timer: Timer) -> Option<Instant> {
self.deadlines[timer.index()]
}
#[must_use]
pub const fn is_armed(&self, timer: Timer) -> bool {
self.deadlines[timer.index()].is_some()
}
#[must_use]
pub fn next_deadline(&self) -> Option<Instant> {
self.deadlines.iter().flatten().copied().min()
}
pub fn expired(&mut self, now: Instant) -> Option<Timer> {
let due = Timer::ALL
.into_iter()
.filter_map(|t| self.deadlines[t.index()].map(|d| (d, t)))
.filter(|&(d, _)| d <= now)
.min();
if let Some((_, timer)) = due {
self.disarm(timer);
return Some(timer);
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nothing_is_pending_on_a_fresh_set() {
let timers = Timers::new();
assert_eq!(timers.next_deadline(), None);
assert!(!timers.is_armed(Timer::Sequence));
}
#[test]
fn the_earliest_deadline_wins() {
let mut t = Timers::new();
let now = Instant::ZERO;
t.arm(Timer::Sequence, now, Millis::from_secs(60));
t.arm(Timer::Ongoing, now, Millis::from_secs(55));
t.arm(Timer::Message, now, Millis::from_secs(2));
assert_eq!(t.next_deadline(), Some(now + Millis::from_secs(2)));
}
#[test]
fn expiry_is_reported_once_and_in_deadline_order() {
let mut t = Timers::new();
let now = Instant::ZERO;
t.arm(Timer::Sequence, now, Millis::from_secs(60));
t.arm(Timer::Message, now, Millis::from_secs(2));
let late = now + Millis::from_secs(61);
assert_eq!(t.expired(late), Some(Timer::Message), "earlier deadline first");
assert_eq!(t.expired(late), Some(Timer::Sequence));
assert_eq!(t.expired(late), None, "each expires exactly once");
}
#[test]
fn a_timer_that_is_not_due_does_not_fire() {
let mut t = Timers::new();
t.arm(Timer::Sequence, Instant::ZERO, Millis::from_secs(60));
assert_eq!(t.expired(Instant::from_millis(59_999)), None);
assert_eq!(t.expired(Instant::from_millis(60_000)), Some(Timer::Sequence));
}
#[test]
fn rearming_replaces_the_old_deadline() {
let mut t = Timers::new();
t.arm(Timer::Sequence, Instant::ZERO, Millis::from_secs(60));
t.arm(Timer::Sequence, Instant::from_millis(30_000), Millis::from_secs(60));
assert_eq!(t.deadline(Timer::Sequence), Some(Instant::from_millis(90_000)));
}
const _: () = {
use iso2 as t2;
use iso20 as t20;
assert!(
t2::SECC_COMMUNICATION_SETUP_PERFORMANCE_TIME.as_millis()
< t2::EVCC_COMMUNICATION_SETUP_TIMEOUT.as_millis()
);
assert!(
t2::SECC_ONGOING_PERFORMANCE_TIME.as_millis() < t2::EVCC_ONGOING_TIMEOUT.as_millis()
);
assert!(
t2::SECC_CABLE_CHECK_PERFORMANCE_TIME.as_millis()
< t2::EVCC_CABLE_CHECK_TIMEOUT.as_millis()
);
assert!(
t2::SECC_PRE_CHARGE_PERFORMANCE_TIME.as_millis()
< t2::EVCC_PRE_CHARGE_TIMEOUT.as_millis()
);
assert!(t20::SECC_ONGOING_PERFORMANCE_TIME.as_millis() < t20::ONGOING_TIMEOUT.as_millis());
assert!(t2::SECC_MSG_PERFORMANCE_DEFAULT.as_millis() < t2::MSG_TIMEOUT_DEFAULT.as_millis());
assert!(t2::SECC_MSG_PERFORMANCE_BACKEND.as_millis() < t2::MSG_TIMEOUT_BACKEND.as_millis());
assert!(
t2::SECC_MSG_PERFORMANCE_CURRENT_DEMAND.as_millis()
< t2::MSG_TIMEOUT_CURRENT_DEMAND.as_millis()
);
assert!(
t20::SECC_EIM_ONGOING_PERFORMANCE_TIME.as_millis()
< t20::EIM_ONGOING_TIMEOUT.as_millis()
);
};
#[test]
fn the_ongoing_window_closes_before_the_sequence_window() {
assert!(iso20::SECC_ONGOING_PERFORMANCE_TIME < iso20::SECC_SEQUENCE_TIMEOUT);
assert!(iso2::SECC_ONGOING_PERFORMANCE_TIME < iso2::SECC_SEQUENCE_TIMEOUT);
}
}