///|
/// State of a deterministic diagnostic workflow.
pub enum DiagnosticWorkflowState {
DiagnosticWorkflowIdle
DiagnosticWorkflowRunning
DiagnosticWorkflowWaiting
DiagnosticWorkflowSucceeded
DiagnosticWorkflowFailed
DiagnosticWorkflowCancelled
}
///|
pub fn diagnostic_workflow_state_variants() -> Array[DiagnosticWorkflowState] {
[
DiagnosticWorkflowIdle,
DiagnosticWorkflowRunning,
DiagnosticWorkflowWaiting,
DiagnosticWorkflowSucceeded,
DiagnosticWorkflowFailed,
DiagnosticWorkflowCancelled,
]
}
///|
/// Retry policy used by a diagnostic workflow step.
pub struct DiagnosticRetryPolicy {
max_attempts : Int
backoff_us : UInt64
retry_negative : Bool
mut attempts : Int
}
///|
pub fn diagnostic_retry_policy(
max_attempts? : Int = 1,
backoff_us? : UInt64 = 0,
retry_negative? : Bool = false,
) -> DiagnosticRetryPolicy {
{
max_attempts: if max_attempts < 1 {
1
} else {
max_attempts
},
backoff_us,
retry_negative,
attempts: 0,
}
}
///|
pub fn DiagnosticRetryPolicy::max_attempts(self : DiagnosticRetryPolicy) -> Int {
self.max_attempts
}
///|
pub fn DiagnosticRetryPolicy::backoff_us(
self : DiagnosticRetryPolicy,
) -> UInt64 {
self.backoff_us
}
///|
pub fn DiagnosticRetryPolicy::retry_negative(
self : DiagnosticRetryPolicy,
) -> Bool {
self.retry_negative
}
///|
pub fn DiagnosticRetryPolicy::attempts(self : DiagnosticRetryPolicy) -> Int {
self.attempts
}
///|
pub fn DiagnosticRetryPolicy::can_retry(self : DiagnosticRetryPolicy) -> Bool {
self.attempts < self.max_attempts
}
///|
pub fn DiagnosticRetryPolicy::begin_attempt(
self : DiagnosticRetryPolicy,
) -> Bool {
if self.can_retry() {
self.attempts += 1
true
} else {
false
}
}
///|
pub fn DiagnosticRetryPolicy::reset(self : DiagnosticRetryPolicy) -> Unit {
self.attempts = 0
}
///|
/// One request scheduled in a diagnostic workflow.
pub struct DiagnosticWorkflowStep {
name : String
request : DiagnosticRequest
timeout_us : UInt64
retry : DiagnosticRetryPolicy
mut sent_at_us : UInt64?
mut completed : Bool
mut result : DiagnosticResponse?
}
///|
pub fn diagnostic_workflow_step(
name : String,
request : DiagnosticRequest,
timeout_us? : UInt64 = 50_000,
retry? : DiagnosticRetryPolicy = diagnostic_retry_policy(),
) -> DiagnosticWorkflowStep {
{
name,
request,
timeout_us,
retry,
sent_at_us: None,
completed: false,
result: None,
}
}
///|
pub fn DiagnosticWorkflowStep::name(self : DiagnosticWorkflowStep) -> String {
self.name
}
///|
pub fn DiagnosticWorkflowStep::request(
self : DiagnosticWorkflowStep,
) -> DiagnosticRequest {
self.request
}
///|
pub fn DiagnosticWorkflowStep::timeout_us(
self : DiagnosticWorkflowStep,
) -> UInt64 {
self.timeout_us
}
///|
pub fn DiagnosticWorkflowStep::retry(
self : DiagnosticWorkflowStep,
) -> DiagnosticRetryPolicy {
self.retry
}
///|
pub fn DiagnosticWorkflowStep::sent_at_us(
self : DiagnosticWorkflowStep,
) -> UInt64? {
self.sent_at_us
}
///|
pub fn DiagnosticWorkflowStep::completed(self : DiagnosticWorkflowStep) -> Bool {
self.completed
}
///|
pub fn DiagnosticWorkflowStep::result(
self : DiagnosticWorkflowStep,
) -> DiagnosticResponse? {
self.result
}
///|
pub fn DiagnosticWorkflowStep::begin(
self : DiagnosticWorkflowStep,
timestamp_us : UInt64,
) -> Bool {
if self.completed || !self.retry.begin_attempt() {
false
} else {
self.sent_at_us = Some(timestamp_us)
true
}
}
///|
pub fn DiagnosticWorkflowStep::accept(
self : DiagnosticWorkflowStep,
payload : Array[Byte],
) -> Bool {
let response = decode_response(self.request, payload)
let positive = match response {
Positive(_) => true
_ => false
}
if positive || !self.retry.retry_negative() {
self.result = Some(response)
self.completed = true
}
positive
}
///|
pub fn DiagnosticWorkflowStep::timed_out(
self : DiagnosticWorkflowStep,
timestamp_us : UInt64,
) -> Bool {
match self.sent_at_us {
Some(sent) => !self.completed && timestamp_us > sent + self.timeout_us
None => false
}
}
///|
pub fn DiagnosticWorkflowStep::clear_attempt(
self : DiagnosticWorkflowStep,
) -> Unit {
self.sent_at_us = None
}
///|
/// A result record retained after a workflow step completes.
pub struct DiagnosticWorkflowResult {
step_name : String
service : String
positive : Bool
attempts : Int
duration_us : UInt64
response : DiagnosticResponse
}
///|
pub fn DiagnosticWorkflowResult::step_name(
self : DiagnosticWorkflowResult,
) -> String {
self.step_name
}
///|
pub fn DiagnosticWorkflowResult::service(
self : DiagnosticWorkflowResult,
) -> String {
self.service
}
///|
pub fn DiagnosticWorkflowResult::positive(
self : DiagnosticWorkflowResult,
) -> Bool {
self.positive
}
///|
pub fn DiagnosticWorkflowResult::attempts(
self : DiagnosticWorkflowResult,
) -> Int {
self.attempts
}
///|
pub fn DiagnosticWorkflowResult::duration_us(
self : DiagnosticWorkflowResult,
) -> UInt64 {
self.duration_us
}
///|
pub fn DiagnosticWorkflowResult::response(
self : DiagnosticWorkflowResult,
) -> DiagnosticResponse {
self.response
}
///|
/// A sequential diagnostic workflow runner.
pub struct DiagnosticWorkflow {
steps : Array[DiagnosticWorkflowStep]
results : Array[DiagnosticWorkflowResult]
mut cursor : Int
mut state : DiagnosticWorkflowState
mut started_us : UInt64
mut last_timestamp_us : UInt64
mut failures : Int
}
///|
pub fn new_diagnostic_workflow() -> DiagnosticWorkflow {
{
steps: [],
results: [],
cursor: 0,
state: DiagnosticWorkflowIdle,
started_us: 0,
last_timestamp_us: 0,
failures: 0,
}
}
///|
pub fn DiagnosticWorkflow::add(
self : DiagnosticWorkflow,
step : DiagnosticWorkflowStep,
) -> Bool {
if self.state is DiagnosticWorkflowIdle {
self.steps.push(step)
true
} else {
false
}
}
///|
pub fn DiagnosticWorkflow::length(self : DiagnosticWorkflow) -> Int {
self.steps.length()
}
///|
pub fn DiagnosticWorkflow::cursor(self : DiagnosticWorkflow) -> Int {
self.cursor
}
///|
pub fn DiagnosticWorkflow::state(
self : DiagnosticWorkflow,
) -> DiagnosticWorkflowState {
self.state
}
///|
pub fn DiagnosticWorkflow::started_us(self : DiagnosticWorkflow) -> UInt64 {
self.started_us
}
///|
pub fn DiagnosticWorkflow::last_timestamp_us(
self : DiagnosticWorkflow,
) -> UInt64 {
self.last_timestamp_us
}
///|
pub fn DiagnosticWorkflow::failures(self : DiagnosticWorkflow) -> Int {
self.failures
}
///|
pub fn DiagnosticWorkflow::results(
self : DiagnosticWorkflow,
) -> Array[DiagnosticWorkflowResult] {
self.results.copy()
}
///|
pub fn DiagnosticWorkflow::start(
self : DiagnosticWorkflow,
timestamp_us : UInt64,
) -> Bool {
if self.steps.is_empty() || !(self.state is DiagnosticWorkflowIdle) {
false
} else {
self.cursor = 0
self.started_us = timestamp_us
self.last_timestamp_us = timestamp_us
self.state = DiagnosticWorkflowRunning
true
}
}
///|
pub fn DiagnosticWorkflow::current(
self : DiagnosticWorkflow,
) -> DiagnosticWorkflowStep? {
if self.cursor < self.steps.length() {
Some(self.steps[self.cursor])
} else {
None
}
}
///|
pub fn DiagnosticWorkflow::begin_current(
self : DiagnosticWorkflow,
timestamp_us : UInt64,
) -> Bool {
match self.current() {
Some(step) => {
self.last_timestamp_us = timestamp_us
step.begin(timestamp_us)
}
None => false
}
}
///|
pub fn DiagnosticWorkflow::accept_current(
self : DiagnosticWorkflow,
payload : Array[Byte],
timestamp_us : UInt64,
) -> Bool {
match self.current() {
Some(step) => {
let positive = step.accept(payload)
self.last_timestamp_us = timestamp_us
if step.completed() {
self.results.push({
step_name: step.name(),
service: step.request().service_name(),
positive,
attempts: step.retry().attempts(),
duration_us: timestamp_us - self.started_us,
response: match step.result() {
Some(value) => value
None => Malformed
},
})
if positive {
self.advance()
} else {
self.failures += 1
self.state = DiagnosticWorkflowFailed
}
}
positive
}
None => false
}
}
///|
pub fn DiagnosticWorkflow::poll_timeout(
self : DiagnosticWorkflow,
timestamp_us : UInt64,
) -> Bool {
match self.current() {
Some(step) =>
if step.timed_out(timestamp_us) {
if step.retry().can_retry() {
step.clear_attempt()
self.state = DiagnosticWorkflowWaiting
true
} else {
self.failures += 1
self.state = DiagnosticWorkflowFailed
false
}
} else {
false
}
None => false
}
}
///|
pub fn DiagnosticWorkflow::continue_after_wait(
self : DiagnosticWorkflow,
) -> Bool {
if self.state is DiagnosticWorkflowWaiting {
self.state = DiagnosticWorkflowRunning
true
} else {
false
}
}
///|
fn DiagnosticWorkflow::advance(self : DiagnosticWorkflow) -> Unit {
self.cursor += 1
if self.cursor >= self.steps.length() {
self.state = DiagnosticWorkflowSucceeded
} else {
self.state = DiagnosticWorkflowRunning
}
}
///|
pub fn DiagnosticWorkflow::cancel(self : DiagnosticWorkflow) -> Unit {
self.state = DiagnosticWorkflowCancelled
}
///|
pub fn DiagnosticWorkflow::reset(self : DiagnosticWorkflow) -> Unit {
for step in self.steps {
step.retry().reset()
step.clear_attempt()
}
self.cursor = 0
self.results.clear()
self.failures = 0
self.state = DiagnosticWorkflowIdle
}
///|
pub fn DiagnosticWorkflow::progress_percent(self : DiagnosticWorkflow) -> Int {
if self.steps.is_empty() {
0
} else {
self.cursor * 100 / self.steps.length()
}
}
///|
pub fn DiagnosticWorkflow::successful(self : DiagnosticWorkflow) -> Bool {
self.state is DiagnosticWorkflowSucceeded
}
///|
pub fn DiagnosticWorkflow::to_text(self : DiagnosticWorkflow) -> String {
"state=" +
diagnostic_workflow_state_text(self.state) +
" progress=" +
self.progress_percent().to_string() +
"% results=" +
self.results.length().to_string() +
" failures=" +
self.failures.to_string()
}
///|
pub fn diagnostic_workflow_state_text(
state : DiagnosticWorkflowState,
) -> String {
match state {
DiagnosticWorkflowIdle => "idle"
DiagnosticWorkflowRunning => "running"
DiagnosticWorkflowWaiting => "waiting"
DiagnosticWorkflowSucceeded => "succeeded"
DiagnosticWorkflowFailed => "failed"
DiagnosticWorkflowCancelled => "cancelled"
}
}
///|
/// A readiness check used before programming or calibration.
pub struct DiagnosticReadiness {
session_ok : Bool
security_ok : Bool
voltage_ok : Bool
temperature_ok : Bool
dtc_ok : Bool
reasons : Array[String]
}
///|
pub fn diagnostic_readiness(
session_ok : Bool,
security_ok : Bool,
voltage_ok : Bool,
temperature_ok : Bool,
dtc_ok : Bool,
) -> DiagnosticReadiness {
let reasons : Array[String] = []
if !session_ok {
reasons.push("invalid diagnostic session")
}
if !security_ok {
reasons.push("security access is locked")
}
if !voltage_ok {
reasons.push("battery voltage outside programming range")
}
if !temperature_ok {
reasons.push("temperature outside calibration range")
}
if !dtc_ok {
reasons.push("blocking DTC is present")
}
{ session_ok, security_ok, voltage_ok, temperature_ok, dtc_ok, reasons }
}
///|
pub fn DiagnosticReadiness::ready(self : DiagnosticReadiness) -> Bool {
self.reasons.is_empty()
}
///|
pub fn DiagnosticReadiness::reasons(
self : DiagnosticReadiness,
) -> Array[String] {
self.reasons.copy()
}
///|
pub fn DiagnosticReadiness::session_ok(self : DiagnosticReadiness) -> Bool {
self.session_ok
}
///|
pub fn DiagnosticReadiness::security_ok(self : DiagnosticReadiness) -> Bool {
self.security_ok
}
///|
pub fn DiagnosticReadiness::voltage_ok(self : DiagnosticReadiness) -> Bool {
self.voltage_ok
}
///|
pub fn DiagnosticReadiness::temperature_ok(self : DiagnosticReadiness) -> Bool {
self.temperature_ok
}
///|
pub fn DiagnosticReadiness::dtc_ok(self : DiagnosticReadiness) -> Bool {
self.dtc_ok
}
///|
/// Aggregate the outcome of multiple readiness checks.
pub fn diagnostic_readiness_all(
checks : Array[DiagnosticReadiness],
) -> DiagnosticReadiness {
let mut session_ok = true
let mut security_ok = true
let mut voltage_ok = true
let mut temperature_ok = true
let mut dtc_ok = true
let reasons : Array[String] = []
for check in checks {
session_ok = session_ok && check.session_ok()
security_ok = security_ok && check.security_ok()
voltage_ok = voltage_ok && check.voltage_ok()
temperature_ok = temperature_ok && check.temperature_ok()
dtc_ok = dtc_ok && check.dtc_ok()
reasons.append(check.reasons())
}
{ session_ok, security_ok, voltage_ok, temperature_ok, dtc_ok, reasons }
}
///|
/// Build a standard ECU identification workflow.
pub fn diagnostic_identification_workflow(
identifiers : Array[UInt],
) -> DiagnosticWorkflow {
let workflow = new_diagnostic_workflow()
for identifier in identifiers {
ignore(
workflow.add(
diagnostic_workflow_step(
"read-data-" + identifier.to_string(),
read_data(identifier),
),
),
)
}
workflow
}