1use crate::vehicle::common::StartStopControl;
2
3use super::*;
4
5#[serde_api]
6#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, StateMethods, SetCumulative)]
7#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
8#[non_exhaustive]
9#[serde(deny_unknown_fields)]
10pub struct DfcoControls {
11 pub dfco_enabled: bool,
13 pub minimum_dfco_speed: si::Velocity,
15 pub minimum_dfco_deceleration: si::Acceleration,
18 #[serde(default = "DfcoControls::def_stopped_speed_threshold")]
20 pub stopped_speed_threshold: si::Velocity,
21 #[serde(default)]
22 pub save_interval: Option<usize>,
24 #[serde(default)]
26 pub state: DfcoState,
27 #[serde(default, skip_serializing_if = "DfcoStateHistoryVec::is_empty")]
29 pub history: DfcoStateHistoryVec,
30}
31
32#[pyo3_api]
33impl DfcoControls {}
34
35impl DfcoControls {
36 fn def_stopped_speed_threshold() -> si::Velocity {
37 0.05 * uc::MPS
38 }
39
40 pub fn is_dfco_disabled_due_to_veh_dynamics(
45 prev_speed: si::Velocity,
46 speed: si::Velocity,
47 dt: si::Time,
48 dfco_allowed: bool,
49 minimum_dfco_speed: si::Velocity,
50 minimum_dfco_deceleration: si::Acceleration,
51 stopped_speed_threshold: si::Velocity,
52 ) -> bool {
53 let decel = (speed - prev_speed) / dt;
54 let is_accel = decel > si::Acceleration::ZERO;
55 if !dfco_allowed {
56 true
57 } else if speed < minimum_dfco_speed {
58 true
59 } else if speed <= stopped_speed_threshold {
60 true
61 } else if is_accel || decel > minimum_dfco_deceleration {
62 true
63 } else {
64 false
66 }
67 }
68}
69
70impl HistoryMethods for DfcoControls {
71 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
72 self.save_interval = save_interval;
73 Ok(())
74 }
75
76 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
77 Ok(self.save_interval)
78 }
79
80 fn clear(&mut self) {
81 self.history.clear();
82 }
83}
84
85impl Init for DfcoControls {
86 fn init(&mut self) -> Result<(), Error> {
87 if self.minimum_dfco_deceleration > si::Acceleration::ZERO {
88 Err(Error::InitError(String::from(
89 "minimum_dfco_acceleration must be <= 0 m/s2",
90 )))
91 } else if self.minimum_dfco_speed < si::Velocity::ZERO {
92 Err(Error::InitError(String::from(
93 "minimum_dfco_speed must be >= 0 m/s",
94 )))
95 } else {
96 Ok(())
97 }
98 }
99}
100
101impl SerdeAPI for DfcoControls {}
102
103impl Default for DfcoControls {
104 fn default() -> Self {
105 Self {
106 dfco_enabled: bool::default(),
107 minimum_dfco_speed: si::Velocity::default(),
108 minimum_dfco_deceleration: si::Acceleration::default(),
109 stopped_speed_threshold: Self::def_stopped_speed_threshold(),
110 save_interval: Option::default(),
111 state: DfcoState::default(),
112 history: DfcoStateHistoryVec::default(),
113 }
114 }
115}
116
117impl DfcoControls {
118 pub fn new(
119 dfco_enabled: bool,
120 minimum_dfco_speed: si::Velocity,
121 minimum_dfco_deceleration: si::Acceleration,
122 save_interval: Option<usize>,
123 ) -> anyhow::Result<Self> {
124 let mut result = Self {
125 dfco_enabled,
126 minimum_dfco_speed,
127 minimum_dfco_deceleration,
128 stopped_speed_threshold: Self::def_stopped_speed_threshold(),
129 save_interval,
130 state: DfcoState::default(),
131 history: DfcoStateHistoryVec::default(),
132 };
133 result.init()?;
134 Ok(result)
135 }
136}
137
138#[serde_api]
139#[derive(
140 Clone,
141 Debug,
142 Default,
143 Deserialize,
144 Serialize,
145 PartialEq,
146 HistoryVec,
147 StateMethods,
148 SetCumulative,
149)]
150#[non_exhaustive]
151#[serde(deny_unknown_fields)]
152pub struct DfcoState {
153 pub i: TrackedState<usize>,
155 pub vehicle_dynamics_prevent_dfco: TrackedState<bool>,
157}
158
159#[serde_api]
160#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, StateMethods, SetCumulative)]
161#[non_exhaustive]
162#[serde(deny_unknown_fields)]
163#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
164pub struct ConventionalVehicle {
166 pub fs: FuelStorage,
167 #[has_state]
168 pub fc: FuelConverter,
169 #[has_state]
170 pub transmission: Transmission,
171 #[has_state]
173 #[serde(default)]
174 pub pt_cntrl: ConvPowertrainControls,
175 #[has_state]
176 #[serde(default)]
177 pub dfco_cntrl: DfcoControls,
178 pub(crate) mass: Option<si::Mass>,
180 pub alt_eff: si::Ratio,
182}
183
184#[pyo3_api]
185impl ConventionalVehicle {}
186
187impl ConventionalVehicle {
188 pub fn new(
189 fs: FuelStorage,
190 fc: FuelConverter,
191 transmission: Transmission,
192 mass: Option<si::Mass>,
193 pt_cntrl: ConvPowertrainControls,
194 dfco_cntrl: DfcoControls,
195 alt_eff: si::Ratio,
196 ) -> anyhow::Result<Self> {
197 let mut conv = Self {
198 fs,
199 fc,
200 transmission,
201 pt_cntrl,
202 dfco_cntrl,
203 mass,
204 alt_eff,
205 };
206 conv.init()?;
207 Ok(conv)
208 }
209}
210
211impl SerdeAPI for ConventionalVehicle {}
212impl Init for ConventionalVehicle {
213 fn init(&mut self) -> Result<(), Error> {
214 self.fc
215 .init()
216 .map_err(|err| Error::InitError(format_dbg!(err)))?;
217 self.fs
218 .init()
219 .map_err(|err| Error::InitError(format_dbg!(err)))?;
220 self.transmission
221 .init()
222 .map_err(|err| Error::InitError(format_dbg!(err)))?;
223 Ok(())
224 }
225}
226
227impl HistoryMethods for ConventionalVehicle {
228 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
229 bail!("`save_interval` is not implemented in ConventionalVehicle")
230 }
231 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
232 self.fc.set_save_interval(save_interval)?;
234 self.transmission.set_save_interval(save_interval)?;
235 Ok(())
236 }
237 fn clear(&mut self) {
238 self.fc.clear();
239 self.transmission.clear();
240 }
241}
242
243impl Powertrain for Box<ConventionalVehicle> {
244 fn set_curr_pwr_prop_out_max(
245 &mut self,
246 _pwr_upstream: (si::Power, si::Power),
247 pwr_aux: si::Power,
248 dt: si::Time,
249 veh_state: &VehicleState,
250 ) -> anyhow::Result<()> {
251 self.fc
253 .set_curr_pwr_out_max(dt)
254 .with_context(|| anyhow!(format_dbg!()))?;
255 self.fc
256 .set_curr_pwr_prop_max(pwr_aux / self.alt_eff)
257 .with_context(|| anyhow!(format_dbg!()))?;
258 self.transmission
259 .set_curr_pwr_prop_out_max(
260 (
261 *self.fc.state.pwr_prop_max.get_fresh(|| format_dbg!())?,
262 si::Power::ZERO,
263 ),
264 f64::NAN * uc::W,
265 dt,
266 veh_state,
267 )
268 .with_context(|| format_dbg!())?;
269 match &mut self.pt_cntrl {
270 ConvPowertrainControls::Normal => (),
271 ConvPowertrainControls::StartStop(ss) => {
272 ss.handle_fc_on_causes(&self.fc, veh_state, dt)?;
273 }
274 }
275 Ok(())
276 }
277
278 fn get_curr_pwr_prop_out_max(&self) -> anyhow::Result<(si::Power, si::Power)> {
279 self.transmission
280 .get_curr_pwr_prop_out_max()
281 .with_context(|| format_dbg!())
282 }
283
284 fn solve(
285 &mut self,
286 pwr_out_req: si::Power,
287 _enabled: bool,
288 dt: si::Time,
289 ) -> anyhow::Result<Option<si::Power>> {
290 ensure!(pwr_out_req >= si::Power::ZERO, format_dbg!());
292 ensure!(almost_le_uom(
293 &pwr_out_req,
294 self.transmission
295 .state
296 .pwr_out_fwd_max
297 .get_fresh(|| format_dbg!())?,
298 None
299 ));
300 ensure!(almost_le_uom(
301 &pwr_out_req,
302 self.transmission
303 .state
304 .pwr_out_fwd_max
305 .get_fresh(|| format_dbg!())?,
306 None
307 ));
308 let pwr_in_transmission = self
309 .transmission
310 .solve(pwr_out_req, true, dt)
311 .with_context(|| format_dbg!())?
312 .with_context(|| format!("{}\nExpected `Some`", format_dbg!()))?;
313 match &mut self.pt_cntrl {
314 ConvPowertrainControls::Normal => (),
315 ConvPowertrainControls::StartStop(ss) => {
316 ConvStartStopControl::handle_fc_on_causes_for_propulsion_request(
317 &mut ss.state.has_traction_power_request,
318 pwr_in_transmission,
319 )?;
320 }
321 }
322 let fc_on: bool = {
323 let fc_on = self.pt_cntrl.fc_on()?;
324 let fc_on_dfco = *self
325 .dfco_cntrl
326 .state
327 .vehicle_dynamics_prevent_dfco
328 .get_fresh(|| format_dbg!())?;
329 let no_tractive_effort_requested = pwr_out_req <= 1e-6 * uc::KW;
330 let fc_off = !fc_on || (!fc_on_dfco && no_tractive_effort_requested);
331 !fc_off
332 };
333 if !fc_on {
334 self.fc.state.pwr_aux.mark_stale();
340 self.fc
341 .state
342 .pwr_aux
343 .update(si::Power::ZERO, || format_dbg!())?;
344 }
345 self.fc
346 .solve(pwr_in_transmission, fc_on, dt)
347 .with_context(|| anyhow!(format_dbg!()))?;
348 Ok(None)
349 }
350
351 fn pwr_regen(&self) -> anyhow::Result<si::Power> {
352 Ok(si::Power::ZERO)
353 }
354}
355
356impl ConventionalVehicle {
357 pub fn solve_thermal(
358 &mut self,
359 te_amb: si::Temperature,
360 pwr_thrml_fc_to_cab: Option<si::Power>,
361 veh_state: &mut VehicleState,
362 dt: si::Time,
363 ) -> anyhow::Result<()> {
364 self.fc
365 .solve_thermal(te_amb, pwr_thrml_fc_to_cab, veh_state, dt)
366 }
367}
368
369impl Mass for ConventionalVehicle {
370 fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
371 let derived_mass = self
372 .derived_mass()
373 .with_context(|| anyhow!(format_dbg!()))?;
374 match (derived_mass, self.mass) {
375 (Some(derived_mass), Some(set_mass)) => {
376 ensure!(
377 utils::almost_eq_uom(&set_mass, &derived_mass, None),
378 format!(
379 "{}",
380 format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
381 )
382 );
383 Ok(Some(set_mass))
384 }
385 _ => Ok(self.mass.or(derived_mass)),
386 }
387 }
388
389 fn set_mass(
390 &mut self,
391 new_mass: Option<si::Mass>,
392 side_effect: MassSideEffect,
393 ) -> anyhow::Result<()> {
394 ensure!(
395 side_effect == MassSideEffect::None,
396 "At the powertrain level, only `MassSideEffect::None` is allowed"
397 );
398 let derived_mass = self
399 .derived_mass()
400 .with_context(|| anyhow!(format_dbg!()))?;
401 self.mass = match (new_mass, derived_mass) {
402 (Some(new_mass), Some(dm)) => {
404 if dm != new_mass {
405 self.expunge_mass_fields();
406 }
407 Some(new_mass)
408 }
409 (Some(new_mass), None) => Some(new_mass),
410 (None, Some(dm)) => Some(dm),
411 (None, None) => bail!(
412 "Not all mass fields in `{}` are set and no mass was provided.",
413 stringify!(ConventionalVehicle)
414 ),
415 };
416 ensure!(
417 self.mass > Some(0.0 * uc::KG),
418 "{} mass must be positive",
419 stringify!(ConventionalVehicle)
420 );
421 Ok(())
422 }
423
424 fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
425 let fc_mass = self.fc.mass().with_context(|| anyhow!(format_dbg!()))?;
426 let fs_mass = self.fs.mass().with_context(|| anyhow!(format_dbg!()))?;
427 let transmission_mass = self
428 .transmission
429 .mass()
430 .with_context(|| anyhow!(format_dbg!()))?;
431 match (fc_mass, fs_mass, transmission_mass) {
432 (Some(fc_mass), Some(fs_mass), Some(transmission_mass)) => {
433 Ok(Some(fc_mass + fs_mass + transmission_mass))
434 }
435 (None, None, None) => Ok(None),
436 _ => bail!(
437 "`{}` field masses are not consistently set to `Some` or `None`",
438 stringify!(ConventionalVehicle)
439 ),
440 }
441 }
442
443 fn expunge_mass_fields(&mut self) {
444 self.fc.expunge_mass_fields();
445 self.fs.expunge_mass_fields();
446 self.transmission.expunge_mass_fields();
447 self.mass = None;
448 }
449}
450
451#[derive(
452 Clone, Debug, PartialEq, Deserialize, Serialize, IsVariant, derive_more::From, TryInto,
453)]
454pub enum ConvPowertrainControls {
455 Normal,
457 #[serde(alias = "StopStart")]
460 StartStop(Box<ConvStartStopControl>),
461}
462
463impl Default for ConvPowertrainControls {
464 fn default() -> Self {
465 Self::Normal
466 }
467}
468
469impl SetCumulative for ConvPowertrainControls {
470 fn set_cumulative<F: Fn() -> String>(&mut self, dt: si::Time, loc: F) -> anyhow::Result<()> {
471 match self {
472 Self::Normal => Ok(()),
473 Self::StartStop(cntrl) => {
474 cntrl.set_cumulative(dt, || format!("{}\n{}", loc(), format_dbg!()))?;
475 Ok(())
476 }
477 }
478 }
479
480 fn reset_cumulative<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
481 match self {
482 Self::Normal => Ok(()),
483 Self::StartStop(cntrl) => {
484 cntrl.reset_cumulative(|| format!("{}\n{}", loc(), format_dbg!()))?;
485 Ok(())
486 }
487 }
488 }
489}
490
491impl Step for ConvPowertrainControls {
492 fn step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
493 match self {
494 Self::Normal => Ok(()),
495 Self::StartStop(cntrl) => cntrl.step(loc),
496 }
497 }
498
499 fn reset_step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
500 match self {
501 Self::Normal => Ok(()),
502 Self::StartStop(cntrls) => cntrls.reset_step(loc),
503 }
504 }
505}
506
507impl StateMethods for ConvPowertrainControls {}
508
509impl SaveState for ConvPowertrainControls {
510 fn save_state<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
511 match self {
512 Self::Normal => Ok(()),
513 Self::StartStop(cntrl) => cntrl.save_state(loc),
514 }
515 }
516}
517
518impl TrackedStateMethods for ConvPowertrainControls {
519 fn check_and_reset<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
520 match self {
521 Self::Normal => Ok(()),
522 Self::StartStop(cntrl) => cntrl.check_and_reset(loc),
523 }
524 }
525
526 fn mark_fresh<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
527 match self {
528 Self::Normal => Ok(()),
529 Self::StartStop(cntrl) => cntrl.mark_fresh(loc),
530 }
531 }
532}
533
534impl HistoryMethods for ConvPowertrainControls {
535 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
536 match self {
537 Self::Normal => Ok(()),
538 Self::StartStop(cntrl) => Ok(cntrl.set_save_interval(save_interval)?),
539 }
540 }
541
542 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
543 match self {
544 Self::Normal => Ok(Option::None),
545 Self::StartStop(cntrl) => cntrl.save_interval(),
546 }
547 }
548
549 fn clear(&mut self) {
550 match self {
551 Self::Normal => (),
552 Self::StartStop(cntrl) => cntrl.clear(),
553 }
554 }
555}
556
557impl Init for ConvPowertrainControls {
558 fn init(&mut self) -> Result<(), Error> {
559 match self {
560 Self::Normal => Ok(()),
561 Self::StartStop(cntrl) => cntrl.init(),
562 }
563 }
564}
565
566impl ConvPowertrainControls {
567 pub fn fc_on(&self) -> anyhow::Result<bool> {
568 match self {
569 Self::Normal => Ok(true),
570 Self::StartStop(cntrl) => cntrl.state.fc_on(),
571 }
572 }
573
574 pub fn handle_fc_on_causes_for_speed(&mut self, speed: si::Velocity) -> anyhow::Result<()> {
575 match self {
576 Self::Normal => Ok(()),
577 Self::StartStop(cntrl) => ConvStartStopControl::handle_fc_on_causes_for_speed(
578 &mut cntrl.state.vehicle_not_stopped,
579 speed,
580 cntrl.stopped_speed_threshold,
581 ),
582 }
583 }
584}
585
586#[serde_api]
587#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, StateMethods, SetCumulative)]
588#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
589#[non_exhaustive]
590#[serde(deny_unknown_fields)]
591pub struct ConvStartStopControl {
592 #[serde(default)]
595 pub fc_min_time_on: Option<si::Time>,
596 #[serde(default)]
598 pub temp_fc_forced_on: Option<si::Temperature>,
599 #[serde(default)]
601 pub temp_fc_allowed_off: Option<si::Temperature>,
602 #[serde(default)]
606 pub time_delay_after_stop_until_fc_can_turn_off: Option<si::Time>,
607 #[serde(default = "ConvStartStopControl::def_stopped_speed_threshold")]
609 pub stopped_speed_threshold: si::Velocity,
610 #[serde(default)]
611 pub save_interval: Option<usize>,
613 #[serde(default)]
615 pub state: ConvStartStopState,
616 #[serde(
618 default,
619 skip_serializing_if = "ConvStartStopStateHistoryVec::is_empty"
620 )]
621 pub history: ConvStartStopStateHistoryVec,
622}
623
624#[pyo3_api]
625impl ConvStartStopControl {}
626
627impl StartStopControl for ConvStartStopControl {}
628
629impl HistoryMethods for ConvStartStopControl {
630 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()> {
631 self.save_interval = save_interval;
632 Ok(())
633 }
634
635 fn save_interval(&self) -> anyhow::Result<Option<usize>> {
636 Ok(self.save_interval)
637 }
638
639 fn clear(&mut self) {
640 self.history.clear();
641 }
642}
643
644impl Init for ConvStartStopControl {
645 fn init(&mut self) -> Result<(), Error> {
646 init_opt_default!(self, fc_min_time_on, uc::S * 5.0);
647 init_opt_default!(
648 self,
649 time_delay_after_stop_until_fc_can_turn_off,
650 0.0 * uc::S
651 );
652 Ok(())
653 }
654}
655
656impl SerdeAPI for ConvStartStopControl {}
657
658impl Default for ConvStartStopControl {
659 fn default() -> Self {
660 Self {
661 fc_min_time_on: Option::default(),
662 temp_fc_forced_on: Option::default(),
663 temp_fc_allowed_off: Option::default(),
664 time_delay_after_stop_until_fc_can_turn_off: Option::default(),
665 stopped_speed_threshold: Self::def_stopped_speed_threshold(),
666 save_interval: Option::default(),
667 state: ConvStartStopState::default(),
668 history: ConvStartStopStateHistoryVec::default(),
669 }
670 }
671}
672
673impl ConvStartStopControl {
674 fn def_stopped_speed_threshold() -> si::Velocity {
675 0.05 * uc::MPS
676 }
677
678 pub fn new(
679 fc_min_time_on: Option<si::Time>,
680 temp_fc_forced_on: Option<si::Temperature>,
681 temp_fc_allowed_off: Option<si::Temperature>,
682 time_delay_after_stop_until_fc_can_turn_off: Option<si::Time>,
683 save_interval: Option<usize>,
684 ) -> anyhow::Result<Self> {
685 let mut result = Self {
686 fc_min_time_on,
687 temp_fc_forced_on,
688 temp_fc_allowed_off,
689 time_delay_after_stop_until_fc_can_turn_off,
690 stopped_speed_threshold: Self::def_stopped_speed_threshold(),
691 save_interval,
692 state: ConvStartStopState::default(),
693 history: ConvStartStopStateHistoryVec::default(),
694 };
695 result.init()?;
696 Ok(result)
697 }
698
699 pub fn handle_fc_on_causes(
700 &mut self,
701 fc: &FuelConverter,
702 veh_state: &VehicleState,
703 dt: si::Time,
704 ) -> anyhow::Result<()> {
705 Self::handle_fc_on_causes_for_stopped_time(
707 &mut self.state.time_vehicle_stopped,
708 &mut self.state.vehicle_not_stopped_long_enough,
709 veh_state,
710 dt,
711 self.time_delay_after_stop_until_fc_can_turn_off,
712 self.stopped_speed_threshold,
713 )?;
714 Self::handle_fc_on_causes_for_temp(
715 fc,
716 self.temp_fc_forced_on,
717 self.temp_fc_allowed_off,
718 &mut self.state.fc_temperature_too_low,
719 )?;
720 Self::handle_fc_on_causes_for_on_time(
722 fc,
723 self.fc_min_time_on,
724 &mut self.state.on_time_too_short,
725 )?;
726 Ok(())
727 }
728}
729
730#[serde_api]
731#[derive(
732 Clone,
733 Debug,
734 Default,
735 Deserialize,
736 Serialize,
737 PartialEq,
738 HistoryVec,
739 StateMethods,
740 SetCumulative,
741)]
742#[non_exhaustive]
743#[serde(deny_unknown_fields)]
744pub struct ConvStartStopState {
745 pub i: TrackedState<usize>,
747 pub fc_temperature_too_low: TrackedState<bool>,
749 pub vehicle_not_stopped: TrackedState<bool>,
751 pub on_time_too_short: TrackedState<bool>,
753 pub time_vehicle_stopped: TrackedState<si::Time>,
755 pub vehicle_not_stopped_long_enough: TrackedState<bool>,
757 pub has_traction_power_request: TrackedState<bool>,
759}
760
761impl ConvStartStopState {
762 fn fc_on(&self) -> anyhow::Result<bool> {
764 let c1 = *self.fc_temperature_too_low.get_fresh(|| format_dbg!())?;
765 let c2 = *self.vehicle_not_stopped.get_fresh(|| format_dbg!())?;
766 let c3 = *self.on_time_too_short.get_fresh(|| format_dbg!())?;
767 let c4 = *self
768 .vehicle_not_stopped_long_enough
769 .get_fresh(|| format_dbg!())?;
770 let c5 = *self
771 .has_traction_power_request
772 .get_fresh(|| format_dbg!())?;
773 Ok(c1 || c2 || c3 || c4 || c5)
774 }
775}
776
777#[cfg(test)]
778pub(crate) mod tests {
779 use super::*;
780
781 fn make_favorable_dfco_conditions() -> (
782 si::Velocity, si::Velocity, si::Time, bool, si::Velocity, si::Acceleration, ) {
789 (
790 40.0 * uc::MPH,
791 36.0 * uc::MPH,
792 1.0 * uc::S,
793 true,
794 20.0 * uc::MPH,
795 0.0 * uc::MPS2,
796 )
797 }
798
799 #[test]
800 fn dfco_activates_when_all_conditions_are_good() {
801 let (prev_speed, speed, dt, dfco_allowed, minimum_dfco_speed, minimum_dfco_deceleration) =
802 make_favorable_dfco_conditions();
803 let result = DfcoControls::is_dfco_disabled_due_to_veh_dynamics(
804 prev_speed,
805 speed,
806 dt,
807 dfco_allowed,
808 minimum_dfco_speed,
809 minimum_dfco_deceleration,
810 0.05 * uc::MPS,
811 );
812 assert_eq!(false, result);
813 }
814
815 #[test]
816 fn dfco_cannot_be_active_if_speed_too_low() {
817 let (_prev_speed, _speed, dt, dfco_allowed, minimum_dfco_speed, minimum_dfco_deceleration) =
818 make_favorable_dfco_conditions();
819 let prev_speed = 10.0 * uc::MPH;
820 let speed = 8.0 * uc::MPH;
821 let result = DfcoControls::is_dfco_disabled_due_to_veh_dynamics(
822 prev_speed,
823 speed,
824 dt,
825 dfco_allowed,
826 minimum_dfco_speed,
827 minimum_dfco_deceleration,
828 0.05 * uc::MPS,
829 );
830 assert_eq!(result, true);
831 }
832
833 #[test]
834 fn dfco_cannot_be_active_if_not_decelerating() {
835 let (prev_speed, _speed, dt, dfco_allowed, minimum_dfco_speed, minimum_dfco_deceleration) =
836 make_favorable_dfco_conditions();
837 let speed = prev_speed + 2.0 * uc::MPH;
838 let result = DfcoControls::is_dfco_disabled_due_to_veh_dynamics(
839 prev_speed,
840 speed,
841 dt,
842 dfco_allowed,
843 minimum_dfco_speed,
844 minimum_dfco_deceleration,
845 0.05 * uc::MPS,
846 );
847 assert_eq!(true, result);
848 }
849
850 #[test]
851 fn dfco_cannot_be_active_if_not_allowed() {
852 let (prev_speed, speed, dt, _dfco_allowed, minimum_dfco_speed, minimum_dfco_deceleration) =
853 make_favorable_dfco_conditions();
854 let dfco_allowed = false;
855 let result = DfcoControls::is_dfco_disabled_due_to_veh_dynamics(
856 prev_speed,
857 speed,
858 dt,
859 dfco_allowed,
860 minimum_dfco_speed,
861 minimum_dfco_deceleration,
862 0.05 * uc::MPS,
863 );
864 assert_eq!(true, result);
865 }
866}