use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use core::fmt;
use crate::app_protocol::SupportedAppProtocolReq;
use crate::message::Message;
use crate::session::{
Connection, ConnectionError, Flow, FlowError, Instant, Millis, SequenceError, SessionId, Timer,
Timers,
};
use crate::trace::{trace_close, trace_event};
use crate::{MAX_EXI_PAYLOAD_LEN, Protocol, Protocols};
#[derive(Debug, Clone)]
pub struct EvccConfig {
pub protocols: Protocols,
pub max_payload_len: usize,
pub setup_timeout: Millis,
pub message_timeout: Option<Millis>,
pub rejoin: Option<SessionId>,
}
impl Default for EvccConfig {
fn default() -> Self {
Self {
protocols: Protocols::ISO,
max_payload_len: MAX_EXI_PAYLOAD_LEN,
setup_timeout: crate::session::timers::iso2::EVCC_COMMUNICATION_SETUP_TIMEOUT,
message_timeout: None,
rejoin: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Event {
ProtocolAgreed(Protocol),
Response(Box<Message>),
Failed,
Closed(Close),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Close {
Normal,
Paused,
Timeout(Timer),
NoCommonProtocol,
}
impl fmt::Display for Close {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Normal => f.write_str("stopped normally"),
Self::Paused => f.write_str("paused"),
Self::Timeout(t) => write!(f, "{} timer expired", t.name()),
Self::NoCommonProtocol => f.write_str("no protocol in common"),
}
}
}
#[derive(Debug)]
pub struct Evcc {
config: EvccConfig,
conn: Connection,
timers: Timers,
flow: Option<Flow>,
events: VecDeque<Event>,
session_id: SessionId,
advertised: Protocols,
outstanding: Option<&'static str>,
established: bool,
phase: Option<&'static str>,
closed: bool,
}
impl Evcc {
#[must_use]
pub fn new(config: EvccConfig) -> Self {
let conn = Connection::with_limit(config.max_payload_len);
let session_id = config.rejoin.unwrap_or(SessionId::NONE);
Self {
config,
conn,
timers: Timers::new(),
flow: None,
events: VecDeque::new(),
session_id,
advertised: Protocols::new(),
outstanding: None,
established: false,
phase: None,
closed: false,
}
}
pub fn start(&mut self, now: Instant) -> Result<(), EvccError> {
let mut advertised = self.config.protocols;
advertised.retain(Flow::supports);
if advertised.is_empty() {
return Err(EvccError::NothingToOffer { configured: self.config.protocols });
}
trace_event!(protocols = %advertised, "advertising");
self.advertised = advertised;
let req = SupportedAppProtocolReq::advertising(advertised);
self.conn.send(&Message::AppProtocolReq(Box::new(req)))?;
self.timers.arm(Timer::CommunicationSetup, now, self.config.setup_timeout);
self.timers.arm(
Timer::Message,
now,
self.config
.message_timeout
.unwrap_or(crate::session::timers::iso2::MSG_TIMEOUT_DEFAULT),
);
self.outstanding = Some("supportedAppProtocolReq");
Ok(())
}
#[must_use]
pub const fn protocol(&self) -> Option<Protocol> {
self.conn.protocol()
}
#[must_use]
pub const fn flow(&self) -> Option<&Flow> {
self.flow.as_ref()
}
#[must_use]
pub const fn session_id(&self) -> SessionId {
self.session_id
}
#[must_use]
pub const fn is_closed(&self) -> bool {
self.closed
}
#[cfg(feature = "iso20-common")]
#[cfg_attr(docsrs, doc(cfg(feature = "iso20-common")))]
pub fn resume(&mut self, service: crate::session::iso20::Service) {
if let Some(flow) = self.flow.as_mut() {
flow.resume(service);
}
}
pub fn request(&mut self, now: Instant, mut request: Message) -> Result<(), EvccError> {
if self.closed {
return Err(EvccError::Closed);
}
if !request.is_request() {
return Err(EvccError::NotARequest(request.name()));
}
if let Some(outstanding) = self.outstanding {
return Err(EvccError::AwaitingResponse { outstanding, got: request.name() });
}
let Some(flow) = self.flow.as_ref() else {
return Err(EvccError::BeforeHandshake(request.name()));
};
flow.permits(&request).map_err(EvccError::Flow)?;
request.set_session_id(self.session_id);
let timeout = self.config.message_timeout.unwrap_or_else(|| response_timeout(&request));
let name = request.name();
self.conn.send(&request)?;
if let Some(flow) = self.flow.as_mut() {
flow.accept(&request).map_err(EvccError::Flow)?;
}
self.enter_phase(now);
trace_event!(message = name, timeout = %timeout, "request sent");
self.timers.arm(Timer::Message, now, timeout);
self.outstanding = Some(name);
Ok(())
}
fn enter_phase(&mut self, now: Instant) {
let Some(flow) = self.flow.as_ref() else { return };
let phase = flow.phase_name();
if self.phase == Some(phase) {
return;
}
self.phase = Some(phase);
match flow.loop_timeout() {
Some(budget) => self.timers.arm(Timer::Ongoing, now, budget),
None => self.timers.disarm(Timer::Ongoing),
}
}
pub fn handle_input(&mut self, data: &[u8]) -> Result<(), EvccError> {
if self.closed {
return Ok(());
}
self.conn.receive(data)?;
while let Some(message) = self.conn.next_message()? {
self.dispatch(message)?;
if self.closed {
break;
}
}
Ok(())
}
fn dispatch(&mut self, message: Message) -> Result<(), EvccError> {
if message.is_request() {
return Err(EvccError::NotAResponse(message.name()));
}
let Some(expected) = self.outstanding else {
return Err(EvccError::UnexpectedResponse { expected: None, got: message.name() });
};
if !message.answers(expected) {
return Err(EvccError::UnexpectedResponse {
expected: Some(expected),
got: message.name(),
});
}
self.timers.disarm(Timer::Message);
self.outstanding = None;
if let Message::AppProtocolRes(res) = &message {
let Some(schema_id) = res.schema_id.filter(|_| res.response_code.is_ok()) else {
self.close(Close::NoCommonProtocol);
return Ok(());
};
let Some(protocol) = self.advertised.iter().nth(usize::from(schema_id).wrapping_sub(1))
else {
return Err(EvccError::UnknownSchemaId(schema_id));
};
let Some(flow) = Flow::new(protocol) else {
self.close(Close::NoCommonProtocol);
return Ok(());
};
self.conn.set_protocol(protocol);
self.flow = Some(flow);
trace_event!(protocol = %protocol, "protocol agreed");
self.events.push_back(Event::ProtocolAgreed(protocol));
return Ok(());
}
match message.session_id() {
Some(id) if !self.established => {
if id.is_none() {
return Err(EvccError::NoSessionAssigned);
}
self.session_id = id;
self.established = true;
self.timers.disarm(Timer::CommunicationSetup);
}
Some(id) if id != self.session_id => {
return Err(EvccError::WrongSession { expected: self.session_id, got: id });
}
_ => {}
}
let failed = message.outcome().is_some_and(crate::message::Outcome::is_failure);
if failed && let Some(flow) = self.flow.as_mut() {
flow.failed();
}
let close = self.flow.as_ref().and_then(|flow| {
if flow.is_paused() {
Some(Close::Paused)
} else if flow.is_finished() {
Some(Close::Normal)
} else {
None
}
});
trace_event!(message = message.name(), "response");
self.events.push_back(Event::Response(Box::new(message)));
if let Some(why) = close {
self.close(why);
} else if failed {
trace_close!("failure response; only SessionStopReq is left");
self.events.push_back(Event::Failed);
}
Ok(())
}
pub fn poll_event(&mut self) -> Option<Event> {
self.events.pop_front()
}
#[must_use]
pub fn poll_timeout(&self) -> Option<Instant> {
self.timers.next_deadline()
}
pub fn handle_timeout(&mut self, now: Instant) {
while let Some(timer) = self.timers.expired(now) {
self.close(Close::Timeout(timer));
}
}
#[must_use]
pub const fn outstanding(&self) -> Option<&'static str> {
self.outstanding
}
#[must_use]
pub fn take_transmit(&mut self) -> Vec<u8> {
self.conn.take_transmit()
}
#[must_use]
pub fn transmit_is_empty(&self) -> bool {
self.conn.transmit_is_empty()
}
fn close(&mut self, why: Close) {
if self.closed {
return;
}
self.closed = true;
self.timers.disarm_all();
trace_close!(reason = %why, "session closed");
self.events.push_back(Event::Closed(why));
}
}
fn response_timeout(request: &Message) -> Millis {
#[cfg(feature = "iso2")]
if let Some(r) = crate::session::iso2::Request::of(request) {
return r.response_timeout();
}
#[cfg(feature = "iso20-common")]
if let Some(r) = crate::session::iso20::Request::of(request) {
return r.response_timeout();
}
let _ = request;
crate::session::timers::iso2::MSG_TIMEOUT_DEFAULT
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EvccError {
Connection(ConnectionError),
Flow(FlowError),
NotAResponse(&'static str),
NotARequest(&'static str),
BeforeHandshake(&'static str),
AwaitingResponse {
outstanding: &'static str,
got: &'static str,
},
UnknownSchemaId(u8),
NothingToOffer {
configured: Protocols,
},
UnexpectedResponse {
expected: Option<&'static str>,
got: &'static str,
},
NoSessionAssigned,
WrongSession {
expected: SessionId,
got: SessionId,
},
Closed,
}
impl From<ConnectionError> for EvccError {
fn from(e: ConnectionError) -> Self {
Self::Connection(e)
}
}
impl From<SequenceError> for EvccError {
fn from(e: SequenceError) -> Self {
Self::Flow(FlowError::Sequence(e))
}
}
impl fmt::Display for EvccError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Connection(e) => write!(f, "{e}"),
Self::Flow(e) => write!(f, "{e}"),
Self::NotAResponse(name) => {
write!(f, "{name} is a request; the EVCC expects responses")
}
Self::NotARequest(name) => write!(f, "{name} is a response, not a request"),
Self::BeforeHandshake(name) => {
write!(f, "{name} cannot be sent before supportedAppProtocol")
}
Self::AwaitingResponse { outstanding, got } => {
write!(f, "{got} cannot be sent while {outstanding} is still unanswered")
}
Self::UnknownSchemaId(id) => {
write!(f, "the charger chose schema id {id}, which was never offered")
}
Self::NothingToOffer { configured } => write!(
f,
"none of the configured protocols ({configured}) has a message set in this build"
),
Self::UnexpectedResponse { expected: Some(req), got } => {
write!(f, "{got} arrived while {req} was outstanding")
}
Self::UnexpectedResponse { expected: None, got } => {
write!(f, "{got} arrived with no request outstanding")
}
Self::NoSessionAssigned => {
f.write_str("the charger assigned the all-zero session id, which names no session")
}
Self::WrongSession { expected, got } => {
write!(f, "a response carried session id {got}, not the assigned {expected}")
}
Self::Closed => f.write_str("the session has ended"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EvccError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Connection(e) => Some(e),
Self::Flow(e) => Some(e),
_ => None,
}
}
}