server_pb/
ota.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
use std::fs;
use std::time::{Duration, Instant};

use async_channel::Sender;
use core_pb::messages::ota::{OverTheAirStep, OverTheAirStepCompletion};
use core_pb::messages::server_status::ServerStatus;
use core_pb::messages::{GuiToServerMessage, RobotToServerMessage, ServerToRobotMessage};
use core_pb::names::{RobotName, NUM_ROBOT_NAMES};
use log::{error, info};

use crate::sockets::{Destination, Incoming, Outgoing};

pub const PACKET_SIZE: usize = 4096;

pub struct OverTheAirProgramming {
    robots: [OverTheAirRobot; NUM_ROBOT_NAMES],
    binary: Vec<u8>,

    tx: Sender<(Destination, Outgoing)>,
}

pub struct OverTheAirRobot {
    name: RobotName,
    start: Instant,
    last_update: Option<Instant>,
}

impl OverTheAirRobot {
    pub fn new(name: RobotName) -> Self {
        Self {
            name,
            start: Instant::now(),
            last_update: None,
        }
    }
}

impl OverTheAirRobot {
    fn update_failed(&mut self, status: &mut ServerStatus) {
        let robot = &mut status.robots[self.name as usize];
        let curr = robot.ota_current;
        robot.ota_completed.push(OverTheAirStepCompletion {
            step: curr,
            since_beginning: self.start.elapsed(),
            success: Some(false),
        });
        robot.ota_current = OverTheAirStep::GuiRequest;
        self.last_update = None;
        for OverTheAirStepCompletion { success, .. } in &mut robot.ota_completed {
            if success.is_none() {
                *success = Some(false)
            }
        }
    }

    fn update_new_in_progress(&mut self, status: &mut ServerStatus) {
        let curr = status.robots[self.name as usize].ota_current;
        status.robots[self.name as usize]
            .ota_completed
            .push(OverTheAirStepCompletion {
                step: curr,
                since_beginning: self.start.elapsed(),
                success: None,
            });
    }

    fn update_completed(&mut self, status: &mut ServerStatus) {
        let curr = status.robots[self.name as usize].ota_current;
        if curr == OverTheAirStep::GuiRequest {
            self.start = Instant::now();
        }
        if status.robots[self.name as usize]
            .ota_completed
            .last()
            .map(|x| x.step != curr)
            .unwrap_or(true)
        {
            status.robots[self.name as usize]
                .ota_completed
                .push(OverTheAirStepCompletion {
                    step: curr,
                    since_beginning: self.start.elapsed(),
                    success: Some(true),
                });
        }
        let last_step: usize = curr.into();
        status.robots[self.name as usize].ota_current = (last_step + 1_usize).into();
        if status.robots[self.name as usize].ota_current == OverTheAirStep::Finished {
            status.robots[self.name as usize]
                .ota_completed
                .push(OverTheAirStepCompletion {
                    step: OverTheAirStep::Finished,
                    since_beginning: self.start.elapsed(),
                    success: Some(true),
                });
            status.robots[self.name as usize].ota_current = OverTheAirStep::GuiRequest;
        }
        self.last_update = None;
    }

    fn update_overwrite(
        &mut self,
        new: OverTheAirStep,
        success: Option<bool>,
        status: &mut ServerStatus,
    ) {
        self.last_update = None;
        if let Some(last) = status.robots[self.name as usize].ota_completed.last_mut() {
            last.step = new;
            last.since_beginning = self.start.elapsed();
            last.success = success;
        }
        status.robots[self.name as usize].ota_current = new;
    }
}

async fn send(tx: &mut Sender<(Destination, Outgoing)>, to: RobotName, msg: ServerToRobotMessage) {
    tx.send((Destination::Robot(to), Outgoing::ToRobot(msg)))
        .await
        .unwrap();
}

impl OverTheAirProgramming {
    pub fn new(tx: Sender<(Destination, Outgoing)>) -> Self {
        Self {
            robots: RobotName::get_all().map(OverTheAirRobot::new),
            binary: vec![],

            tx,
        }
    }

    async fn send_firmware_part(&mut self, to: RobotName, offset: usize) {
        let next_packet_len = if offset + PACKET_SIZE > self.binary.len() {
            self.binary.len() - offset
        } else {
            PACKET_SIZE
        };
        self.tx
            .send((
                Destination::Robot(to),
                Outgoing::ToRobot(ServerToRobotMessage::FirmwareWritePart {
                    offset,
                    len: next_packet_len,
                }),
            ))
            .await
            .unwrap();
        self.tx
            .send((
                Destination::Robot(to),
                Outgoing::RawBytes(self.binary[offset..offset + next_packet_len].to_vec()),
            ))
            .await
            .unwrap();
    }

    async fn cancel_update(&mut self, name: RobotName, status: &mut ServerStatus) {
        send(
            &mut self.tx,
            name,
            ServerToRobotMessage::CancelFirmwareUpdate,
        )
        .await;
        self.robots[name as usize].update_failed(status);
    }

    /// Retry operations if necessary; should be called frequently
    pub async fn tick(&mut self, status: &mut ServerStatus) {
        for name in RobotName::get_all() {
            let do_update = match self.robots[name as usize].last_update {
                None => true,
                Some(t) => {
                    t.elapsed()
                        > match status.robots[name as usize].ota_current {
                            OverTheAirStep::DataTransfer { .. } => Duration::from_millis(2000),
                            _ => Duration::from_millis(5000),
                        }
                }
            } && status.robots[name as usize].ota_current
                != OverTheAirStep::GuiRequest;
            if do_update {
                self.robots[name as usize].last_update = Some(Instant::now());
                let msg = match status.robots[name as usize].ota_current {
                    OverTheAirStep::RobotReadyConfirmation => {
                        Some(ServerToRobotMessage::ReadyToStartUpdate)
                    }
                    OverTheAirStep::DataTransfer { received, .. } => {
                        self.send_firmware_part(name, received).await;
                        None
                    }
                    OverTheAirStep::HashConfirmation => {
                        Some(ServerToRobotMessage::CalculateFirmwareHash(0))
                    }
                    OverTheAirStep::MarkUpdateReady => {
                        Some(ServerToRobotMessage::MarkFirmwareUpdated)
                    }
                    OverTheAirStep::Reboot => Some(ServerToRobotMessage::Reboot),
                    OverTheAirStep::CheckFirmwareSwapped => {
                        Some(ServerToRobotMessage::IsFirmwareSwapped)
                    }
                    OverTheAirStep::MarkUpdateBooted => {
                        Some(ServerToRobotMessage::MarkFirmwareBooted)
                    }
                    OverTheAirStep::GuiRequest
                    | OverTheAirStep::GuiConfirmation
                    | OverTheAirStep::FinalGuiConfirmation
                    | OverTheAirStep::Finished
                    | OverTheAirStep::Failed
                    | OverTheAirStep::FetchBinary => None,
                };
                if let Some(msg) = msg {
                    send(&mut self.tx, name, msg).await;
                }
            }
        }
    }

    /// Pass all incoming messages through this function; most will do nothing
    pub async fn update(&mut self, msg: &(Destination, Incoming), status: &mut ServerStatus) {
        match msg {
            // gui requests firmware update
            (_, Incoming::FromGui(GuiToServerMessage::StartOtaFirmwareUpdate(name))) => {
                if status.robots[*name as usize].ota_current != OverTheAirStep::GuiRequest {
                    error!(
                        "Firmware update was requested for {name} when one was already in progress"
                    );
                    self.cancel_update(*name, status).await;
                }
                // start update
                status.robots[*name as usize].ota_completed.clear();
                self.robots[*name as usize].update_completed(status);
                self.tick(status).await;
            }
            // gui cancels firmware update
            (_, Incoming::FromGui(GuiToServerMessage::CancelOtaFirmwareUpdate(name))) => {
                self.cancel_update(*name, status).await;
            }
            (_, Incoming::FromGui(GuiToServerMessage::ClearFirmwareUpdateHistory(name))) => {
                status.robots[*name as usize].ota_completed.clear();
            }
            // message from robot
            (Destination::Robot(name), Incoming::FromRobot(msg)) => match msg {
                // robot indicates that it is ready for the update
                RobotToServerMessage::ReadyToStartUpdate => {
                    info!("[server] {name} ready to start update");
                    if status.robots[*name as usize].ota_current
                        == OverTheAirStep::RobotReadyConfirmation
                    {
                        self.robots[*name as usize].update_completed(status);
                        // read binary
                        match fs::read(
                            env!("CARGO_MANIFEST_DIR").to_string() + "/../pico_pb/latest.bin",
                        ) {
                            Ok(bytes) => {
                                self.binary = bytes;
                                self.robots[*name as usize].update_completed(status);
                                if let OverTheAirStep::DataTransfer { total, .. } =
                                    &mut status.robots[*name as usize].ota_current
                                {
                                    *total = self.binary.len();
                                }
                                // send first packet
                                self.tick(status).await;
                                self.robots[*name as usize].update_new_in_progress(status);
                            }
                            Err(e) => {
                                error!("Error reading binary for robot: {e:?}");
                                self.robots[*name as usize].update_failed(status);
                            }
                        }
                    }
                }
                // robot indicates it has received the firmware part
                RobotToServerMessage::ConfirmFirmwarePart { offset, len } => {
                    if let OverTheAirStep::DataTransfer { received, total } =
                        status.robots[*name as usize].ota_current
                    {
                        if *offset != received {
                            self.robots[*name as usize].update_failed(status);
                            error!(
                                "Robot received bytes at the wrong offset: {} != {}",
                                *offset, received
                            );
                            self.cancel_update(*name, status).await;
                        } else {
                            // is there another firmware part?
                            if *offset + *len < total {
                                self.robots[*name as usize].update_overwrite(
                                    OverTheAirStep::DataTransfer {
                                        received: *offset + *len,
                                        total: self.binary.len(),
                                    },
                                    None,
                                    status,
                                );
                                // send next packet
                                self.tick(status).await;
                            } else {
                                // we are finished sending the bytes
                                self.robots[*name as usize].update_overwrite(
                                    OverTheAirStep::DataTransfer {
                                        received: self.binary.len(),
                                        total: self.binary.len(),
                                    },
                                    Some(true),
                                    status,
                                );
                                self.robots[*name as usize].update_completed(status);
                                self.tick(status).await;
                            }
                        }
                    }
                }
                // robot sends hash back
                // todo confirm this
                RobotToServerMessage::FirmwareHash(_) => {
                    if status.robots[*name as usize].ota_current == OverTheAirStep::HashConfirmation
                    {
                        self.robots[*name as usize].update_completed(status);
                        // wait for a gui to confirm update
                    }
                }
                // robot has marked the new firmware to be used on boot
                RobotToServerMessage::MarkedFirmwareUpdated => {
                    self.complete_if_currently(name, OverTheAirStep::MarkUpdateReady, status)
                        .await;
                }
                RobotToServerMessage::Rebooting => {
                    self.complete_if_currently(name, OverTheAirStep::Reboot, status)
                        .await;
                }
                RobotToServerMessage::FirmwareIsSwapped(swapped) => {
                    if status.robots[*name as usize].ota_current
                        == OverTheAirStep::CheckFirmwareSwapped
                    {
                        if *swapped {
                            self.robots[*name as usize].update_completed(status);
                            self.tick(status).await;
                        } else {
                            error!("Robot {name} seems to have rebooted, but its firmware wasn't swapped. Did it crash?");
                            self.robots[*name as usize].update_failed(status);
                        }
                    }
                }
                RobotToServerMessage::MarkedFirmwareBooted => {
                    self.complete_if_currently(name, OverTheAirStep::MarkUpdateBooted, status)
                        .await;
                }
                _ => {}
            },
            (_, Incoming::FromGui(GuiToServerMessage::ConfirmFirmwareUpdate(name))) => {
                self.complete_if_currently(name, OverTheAirStep::GuiConfirmation, status)
                    .await;
                self.complete_if_currently(name, OverTheAirStep::FinalGuiConfirmation, status)
                    .await;
            }
            _ => {}
        }
    }

    async fn complete_if_currently(
        &mut self,
        name: &RobotName,
        current: OverTheAirStep,
        status: &mut ServerStatus,
    ) {
        if status.robots[*name as usize].ota_current == current {
            self.robots[*name as usize].update_completed(status);
            self.tick(status).await;
        }
    }
}