rustot/ota/
agent.rs

1use super::{
2    builder::{self, NoTimer},
3    control_interface::ControlInterface,
4    data_interface::{DataInterface, NoInterface},
5    encoding::json::OtaJob,
6    pal::OtaPal,
7    state::{Error, Events, JobEventData, SmContext, StateMachine, States},
8};
9use crate::jobs::StatusDetails;
10
11// OTA Agent driving the FSM of an OTA update
12pub struct OtaAgent<'a, C, DP, DS, T, ST, PAL, const TIMER_HZ: u32>
13where
14    C: ControlInterface,
15    DP: DataInterface,
16    DS: DataInterface,
17    T: fugit_timer::Timer<TIMER_HZ>,
18    ST: fugit_timer::Timer<TIMER_HZ>,
19    PAL: OtaPal,
20{
21    pub(crate) state: StateMachine<SmContext<'a, C, DP, DS, T, ST, PAL, 3, TIMER_HZ>>,
22}
23
24// Make sure any active OTA session is cleaned up, and the topics are
25// unsubscribed on drop.
26impl<'a, C, DP, DS, T, ST, PAL, const TIMER_HZ: u32> Drop
27    for OtaAgent<'a, C, DP, DS, T, ST, PAL, TIMER_HZ>
28where
29    C: ControlInterface,
30    DP: DataInterface,
31    DS: DataInterface,
32    T: fugit_timer::Timer<TIMER_HZ>,
33    ST: fugit_timer::Timer<TIMER_HZ>,
34    PAL: OtaPal,
35{
36    fn drop(&mut self) {
37        let sm_context = self.state.context_mut();
38        sm_context.ota_close().ok();
39        sm_context.control.cleanup().ok();
40    }
41}
42
43impl<'a, C, DP, T, PAL, const TIMER_HZ: u32>
44    OtaAgent<'a, C, DP, NoInterface, T, NoTimer, PAL, TIMER_HZ>
45where
46    C: ControlInterface,
47    DP: DataInterface,
48    T: fugit_timer::Timer<TIMER_HZ>,
49    PAL: OtaPal,
50{
51    pub fn builder(
52        control_interface: &'a C,
53        data_primary: DP,
54        request_timer: T,
55        pal: PAL,
56    ) -> builder::OtaAgentBuilder<'a, C, DP, NoInterface, T, NoTimer, PAL, TIMER_HZ> {
57        builder::OtaAgentBuilder::new(control_interface, data_primary, request_timer, pal)
58    }
59}
60
61/// Public interface of the OTA Agent
62impl<'a, C, DP, DS, T, ST, PAL, const TIMER_HZ: u32> OtaAgent<'a, C, DP, DS, T, ST, PAL, TIMER_HZ>
63where
64    C: ControlInterface,
65    DP: DataInterface,
66    DS: DataInterface,
67    T: fugit_timer::Timer<TIMER_HZ>,
68    ST: fugit_timer::Timer<TIMER_HZ>,
69    PAL: OtaPal,
70{
71    pub fn init(&mut self) {
72        if matches!(self.state(), &States::Ready) {
73            self.state.process_event(Events::Start).ok();
74        } else {
75            self.state.process_event(Events::Resume).ok();
76        }
77    }
78
79    pub fn job_update(
80        &mut self,
81        job_name: &str,
82        ota_document: &OtaJob,
83        status_details: Option<&StatusDetails>,
84    ) -> Result<&States, Error> {
85        self.state
86            .process_event(Events::ReceivedJobDocument(JobEventData {
87                job_name,
88                ota_document,
89                status_details,
90            }))
91    }
92
93    pub fn timer_callback(&mut self) -> Result<(), Error> {
94        let ctx = self.state.context_mut();
95        if ctx.request_timer.wait().is_ok() {
96            return self.state.process_event(Events::RequestTimer).map(drop);
97        }
98
99        if let Some(ref mut self_test_timer) = ctx.self_test_timer {
100            if self_test_timer.wait().is_ok() {
101                error!(
102                    "Self test failed to complete within {} ms",
103                    ctx.config.self_test_timeout_ms
104                );
105                ctx.pal.reset_device().ok();
106            }
107        }
108        Ok(())
109    }
110
111    pub fn process_event(&mut self) -> Result<&States, Error> {
112        if let Some(event) = self.state.context_mut().events.dequeue() {
113            self.state.process_event(event)
114        } else {
115            Ok(self.state())
116        }
117    }
118
119    pub fn handle_message(&mut self, payload: &mut [u8]) -> Result<&States, Error> {
120        self.state.process_event(Events::ReceivedFileBlock(payload))
121    }
122
123    pub fn check_for_update(&mut self) -> Result<&States, Error> {
124        if matches!(
125            self.state(),
126            States::WaitingForJob | States::RequestingJob | States::WaitingForFileBlock
127        ) {
128            self.state.process_event(Events::RequestJobDocument)
129        } else {
130            Ok(self.state())
131        }
132    }
133
134    pub fn abort(&mut self) -> Result<&States, Error> {
135        self.state.process_event(Events::UserAbort)
136    }
137
138    pub fn suspend(&mut self) -> Result<&States, Error> {
139        // Stop the request timer
140        self.state.context_mut().request_timer.cancel().ok();
141
142        // Send event to OTA agent task.
143        self.state.process_event(Events::Suspend)
144    }
145
146    pub fn resume(&mut self) -> Result<&States, Error> {
147        // Send event to OTA agent task
148        self.state.process_event(Events::Resume)
149    }
150
151    pub fn state(&self) -> &States {
152        self.state.state()
153    }
154}