Skip to content

Commit

Permalink
Merge pull request #10 from esden/dfu
Browse files Browse the repository at this point in the history
Dfu Requests
  • Loading branch information
gauteh authored Dec 30, 2023
2 parents d0fac22 + b9b10f6 commit 44e7fc6
Show file tree
Hide file tree
Showing 2 changed files with 330 additions and 0 deletions.
324 changes: 324 additions & 0 deletions src/dfu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
//! https://dev.blues.io/api-reference/notecard-api/dfu-requests/
#[allow(unused_imports)]
use defmt::{debug, error, info, trace, warn};
use embedded_hal::blocking::i2c::{Read, SevenBitAddress, Write};
use embedded_hal::blocking::delay::DelayMs;
use serde::{Deserialize, Serialize};

use super::{FutureResponse, NoteError, Notecard};

pub struct DFU<'a, IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BS: usize> {
note: &'a mut Notecard<IOM, BS>,
}

impl<'a, IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BS: usize> DFU<'a, IOM, BS> {
pub fn from(note: &mut Notecard<IOM, BS>) -> DFU<'_, IOM, BS> {
DFU { note }
}

/// Retrieves downloaded firmware data from the Notecard.
/// Note: this request is functional only when the Notecard has been set to
/// dfu mode with a `hub.set`, `mode:dfu` request.
pub fn get<const PS: usize>(self, delay: &mut impl DelayMs<u16>, length: usize, offset: Option<usize>) -> Result<FutureResponse<'a, res::Get<PS>, IOM, BS>, NoteError> {
self.note.request(delay, req::Get {
req: "dfu.get",
length,
offset,
})?;

Ok(FutureResponse::from(self.note))
}

/// Gets and sets the background download status of MCU host or Notecard
/// firmware updates.
pub fn status(
self,
delay: &mut impl DelayMs<u16>,
name: Option<req::StatusName>,
stop: Option<bool>,
status: Option<&str>,
version: Option<&str>,
vvalue: Option<&str>, // This is not JSON :(
on: Option<bool>,
err: Option<&str>
) -> Result<FutureResponse<'a, res::Status, IOM, BS>, NoteError> {
self.note.request(delay, req::Status::new(
name,
stop,
status,
version,
vvalue,
on,
err
))?;

Ok(FutureResponse::from(self.note))
}
}

pub mod req {
use super::*;

#[derive(Serialize, Deserialize, defmt::Format, Default)]
pub struct Get {
pub req: &'static str,

pub length: usize,

#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<usize>
}


#[derive(Serialize, Deserialize, defmt::Format, PartialEq, Debug)]
#[serde(rename_all = "lowercase")]
pub enum StatusName {
User,
Card
}

#[derive(Serialize, Deserialize, defmt::Format)]
pub struct Version<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub org: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub product: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firmware: Option<&'a str>,
pub version: &'a str,
pub ver_major: u32,
pub ver_minor: u32,
pub ver_patch: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub ver_build: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub built: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub builder: Option<&'a str>,
}

#[derive(Serialize, Deserialize, defmt::Format, Default)]
pub struct Status<'a> {
pub req: &'static str,

#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<StatusName>,

#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<&'a str>,

#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<&'a str>,

#[serde(skip_serializing_if = "Option::is_none")]
pub vvalue: Option<&'a str>,

#[serde(skip_serializing_if = "Option::is_none")]
pub on: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
pub off: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
pub err: Option<&'a str>,
}

impl Status<'_> {
pub fn new<'a>(
name: Option<req::StatusName>,
stop: Option<bool>,
status: Option<&'a str>,
version: Option<&'a str>,
vvalue: Option<&'a str>, // This is not JSON :(
on: Option<bool>,
err: Option<&'a str>
) -> Status<'a> {
// The `on`/`off` parameters are exclusive
// When on is `true` we set `on` to `Some(True)` and `off` to `None`.
// When on is `false` we set `on` to `None` and `off` to `Some(True)`.
// This way we are not sending the `on` and `off` parameters together.
Status {
req: "dfu.status",
name,
stop,
status,
version,
vvalue,
on: on.and_then(|v| v.then_some(true)),
off: on.and_then(|v| (!v).then_some(true)),
err,
}
}
}
}

pub mod res {
use super::*;

#[derive(Deserialize, defmt::Format)]
pub struct Get<const PS: usize> {
pub payload: heapless::String<PS>
}

#[derive(Deserialize, defmt::Format, PartialEq, Debug)]
#[serde(rename_all = "lowercase")]
pub enum StatusMode {
Idle,
Error,
Downloading,
Ready
}

#[derive(Deserialize, defmt::Format)]
pub struct StatusBody {
pub crc32: Option<u32>,
pub created: Option<u32>,
//pub info: JSON?
pub length: Option<usize>,
pub md5: Option<heapless::String<32>>,
pub modified: Option<u32>,
pub name: Option<heapless::String<120>>,
pub notes: Option<heapless::String<120>>,
pub source: Option<heapless::String<120>>,
#[serde(rename = "type")]
pub bin_type: Option<heapless::String<120>>,
}

#[derive(Deserialize, defmt::Format)]
pub struct Status {
pub mode: StatusMode,
pub status: Option<heapless::String<120>>,
pub on: Option<bool>,
pub off: Option<bool>,
pub pending: Option<bool>,
pub body: Option<StatusBody>
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_get() {
let (res, _) = serde_json_core::from_str::<res::Get<32>>(r#"{"payload":"THISISALOTOFBINARYDATA="}"#).unwrap();
assert_eq!(res.payload, r#"THISISALOTOFBINARYDATA="#);
}

#[test]
fn test_status_name() {
let res: heapless::String<32> = serde_json_core::to_string(&req::StatusName::Card).unwrap();
assert_eq!(res, r#""card""#);
let res: heapless::String<32> = serde_json_core::to_string(&req::StatusName::User).unwrap();
assert_eq!(res, r#""user""#);
}

#[test]
fn test_status_req() {
// Test basic request
let req = req::Status::new(
None,
None,
None,
None,
None,
None,
None
);
let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"dfu.status"}"#);

// Test a bunch of fields set
let ver = req::Version {
org: Some("Organization"),
product: Some("Product"),
description: Some("Firmware Description"),
firmware: Some("Firmware Name"),
version: "Firmware Version 1.0.0",
ver_major: 1,
ver_minor: 0,
ver_patch: 0,
ver_build: Some(12345),
built: Some("Some Sunny Day In December"),
builder: Some("The Compnay"),
};
let ver_str: heapless::String<512> = serde_json_core::to_string(&ver).unwrap();
let req = req::Status::new(
Some(req::StatusName::User),
Some(true),
Some("test status"),
Some(ver_str.as_str()),
Some("usb:1;high:1;normal:1;low:0;dead:0"),
Some(true),
Some("test error"),
);
let res: heapless::String<512> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"dfu.status","name":"user","stop":true,"status":"test status","version":"{\"org\":\"Organization\",\"product\":\"Product\",\"description\":\"Firmware Description\",\"firmware\":\"Firmware Name\",\"version\":\"Firmware Version 1.0.0\",\"ver_major\":1,\"ver_minor\":0,\"ver_patch\":0,\"ver_build\":12345,\"built\":\"Some Sunny Day In December\",\"builder\":\"The Compnay\"}","vvalue":"usb:1;high:1;normal:1;low:0;dead:0","on":true,"err":"test error"}"#);

// Test off set
let req = req::Status::new(
None,
None,
None,
None,
None,
Some(false),
None
);
let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"dfu.status","off":true}"#);
}

#[test]
fn test_status_mode() {
let (res, _) = serde_json_core::from_str::<res::StatusMode>(r#""downloading""#).unwrap();
assert_eq!(res, res::StatusMode::Downloading);
let (res, _) = serde_json_core::from_str::<res::StatusMode>(r#""error""#).unwrap();
assert_eq!(res, res::StatusMode::Error);
let (res, _) = serde_json_core::from_str::<res::StatusMode>(r#""idle""#).unwrap();
assert_eq!(res, res::StatusMode::Idle);
let (res, _) = serde_json_core::from_str::<res::StatusMode>(r#""ready""#).unwrap();
assert_eq!(res, res::StatusMode::Ready);
}

#[test]
fn test_status() {
let (res, _) = serde_json_core::from_str::<res::Status>(r#"{
"mode": "ready",
"status": "successfully downloaded",
"on": true,
"body": {
"crc32": 2525287425,
"created": 1599163431,
"info": {},
"length": 42892,
"md5": "5a3f73a7f1b4bc8917b12b36c2532969",
"modified": 1599163431,
"name": "stm32-new-firmware$20200903200351.bin",
"notes": "Latest prod firmware",
"source": "stm32-new-firmware.bin",
"type": "firmware"
}
}"#).unwrap();

assert_eq!(res.mode, res::StatusMode::Ready);
assert_eq!(res.status.unwrap(), "successfully downloaded");
assert_eq!(res.on.unwrap(), true);
let body = res.body.unwrap();
assert_eq!(body.crc32.unwrap(), 2525287425);
assert_eq!(body.created.unwrap(), 1599163431);
assert_eq!(body.length.unwrap(), 42892);
assert_eq!(body.md5.unwrap(), "5a3f73a7f1b4bc8917b12b36c2532969");
assert_eq!(body.modified.unwrap(), 1599163431);
assert_eq!(body.name.unwrap(), "stm32-new-firmware$20200903200351.bin");
assert_eq!(body.notes.unwrap(), "Latest prod firmware");
assert_eq!(body.source.unwrap(), "stm32-new-firmware.bin");
assert_eq!(body.bin_type.unwrap(), "firmware");
}
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub mod card;
pub mod hub;
pub mod note;
pub mod dfu;

/// Delay between polling for new response.
const RESPONSE_DELAY: u16 = 25;
Expand Down Expand Up @@ -527,6 +528,11 @@ impl<IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BUF_SIZE: usize>
pub fn hub(&mut self) -> hub::Hub<IOM, BUF_SIZE> {
hub::Hub::from(self)
}

/// [dfu Requests](https://dev.blues.io/api-reference/notecard-api/dfu-requests/)
pub fn dfu(&mut self) -> dfu::DFU<IOM, BUF_SIZE> {
dfu::DFU::from(self)
}
}

/// A future response.
Expand Down

0 comments on commit 44e7fc6

Please sign in to comment.