Compare commits

11 Commits

8 changed files with 162 additions and 401 deletions

157
Cargo.lock generated
View File

@@ -187,21 +187,6 @@ dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "bootloader"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"defmt",
"defmt-rtt",
"embassy-boot-rp",
"embassy-rp",
"embassy-sync",
"embassy-time",
"embedded-storage",
]
[[package]]
name = "buffered-io"
version = "0.2.1"
@@ -293,7 +278,6 @@ checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [
"bare-metal",
"bitfield",
"critical-section",
"embedded-hal 0.2.7",
"volatile-register",
]
@@ -563,6 +547,15 @@ dependencies = [
"serde",
]
[[package]]
name = "ed25519"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7"
dependencies = [
"signature",
]
[[package]]
name = "either"
version = "1.9.0"
@@ -599,6 +592,7 @@ dependencies = [
"embassy-sync",
"embedded-storage",
"embedded-storage-async",
"salty",
"signature",
]
@@ -624,6 +618,7 @@ name = "embassy-embedded-hal"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy#af7c93abba768057f1d3299c7b4f4aa4501b3e56"
dependencies = [
"defmt",
"embassy-futures",
"embassy-sync",
"embassy-time",
@@ -784,21 +779,6 @@ dependencies = [
"heapless 0.7.16",
]
[[package]]
name = "embassy-usb"
version = "0.1.0"
source = "git+https://github.com/embassy-rs/embassy#af7c93abba768057f1d3299c7b4f4aa4501b3e56"
dependencies = [
"defmt",
"embassy-futures",
"embassy-net-driver-channel",
"embassy-sync",
"embassy-usb-driver",
"heapless 0.7.16",
"ssmarshal",
"usbd-hid",
]
[[package]]
name = "embassy-usb-driver"
version = "0.1.0"
@@ -931,12 +911,6 @@ dependencies = [
"log",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "equivalent"
version = "1.0.1"
@@ -1257,6 +1231,30 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "impl-tools"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82c305b1081f1a99fda262883c788e50ab57d36c00830bdd7e0a82894ad965c"
dependencies = [
"autocfg",
"impl-tools-lib",
"proc-macro-error",
"syn 2.0.29",
]
[[package]]
name = "impl-tools-lib"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85d3946d886eaab0702fa0c6585adcced581513223fa9df7ccfabbd9fa331a88"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "indexmap"
version = "2.0.0"
@@ -1761,7 +1759,6 @@ dependencies = [
"const_format",
"cortex-m",
"cortex-m-rt",
"crc16",
"cyw43",
"cyw43-pio",
"defmt",
@@ -1770,27 +1767,24 @@ dependencies = [
"dsmr5",
"embassy-boot",
"embassy-boot-rp",
"embassy-embedded-hal",
"embassy-executor",
"embassy-futures",
"embassy-net",
"embassy-rp",
"embassy-sync",
"embassy-time",
"embassy-usb",
"embedded-io-async",
"embedded-storage",
"git-version",
"heapless 0.7.16",
"log",
"nourl",
"panic-probe",
"rand",
"reqwless",
"rust-mqtt",
"serde",
"serde-json-core",
"smoltcp",
"static_cell",
"updater",
]
[[package]]
@@ -1866,6 +1860,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "salty"
version = "0.2.0"
source = "git+https://github.com/ycrypto/salty.git?rev=a9f17911a5024698406b75c0fac56ab5ccf6a8c7#a9f17911a5024698406b75c0fac56ab5ccf6a8c7"
dependencies = [
"ed25519",
"subtle",
"zeroize",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1989,16 +1993,6 @@ dependencies = [
"lock_api",
]
[[package]]
name = "ssmarshal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850"
dependencies = [
"encode_unicode",
"serde",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -2145,44 +2139,29 @@ dependencies = [
]
[[package]]
name = "usb-device"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508"
[[package]]
name = "usbd-hid"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132"
name = "updater"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"defmt",
"defmt-rtt",
"embassy-boot",
"embassy-futures",
"embassy-net",
"embassy-time",
"embedded-io-async",
"embedded-storage",
"embedded-tls",
"heapless 0.7.16",
"impl-tools",
"nourl",
"rand_core",
"reqwless",
"rust-mqtt",
"serde",
"ssmarshal",
"usb-device",
"usbd-hid-macros",
]
[[package]]
name = "usbd-hid-descriptors"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1"
dependencies = [
"bitfield",
]
[[package]]
name = "usbd-hid-macros"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2"
dependencies = [
"byteorder",
"proc-macro2",
"quote",
"serde",
"syn 1.0.109",
"usbd-hid-descriptors",
"serde-json-core",
"static_cell",
]
[[package]]

View File

@@ -3,9 +3,6 @@ name = "rp"
version = "0.1.0"
edition = "2021"
[workspace]
members = ["bootloader"]
[dependencies]
cortex-m = { version = "0.7", features = ["inline-asm"] }
cortex-m-rt = "0.7"
@@ -19,6 +16,7 @@ embassy-executor = { version = "0.3", features = [
"nightly",
"integrated-timers",
] }
embassy-embedded-hal = { version = "0.1", features = ["defmt", "nightly"] }
embassy-rp = { version = "0.1", features = [
"defmt",
"unstable-traits",
@@ -28,14 +26,17 @@ embassy-rp = { version = "0.1", features = [
"critical-section-impl",
] }
embassy-boot-rp = { version = "0.1", features = ["nightly", "defmt"] }
embassy-boot = { version = "0.1", features = ["nightly", "defmt"] }
embassy-boot = { version = "0.1", features = [
"nightly",
"defmt",
"ed25519-salty",
] }
embassy-time = { version = "0.1", features = [
"defmt",
"unstable-traits",
"defmt-timestamp-uptime",
"nightly",
] }
embassy-usb = { version = "0.1", features = ["defmt"] }
embassy-net = { version = "0.1", features = [
"tcp",
"dhcpv4",
@@ -54,11 +55,8 @@ cyw43-pio = { git = "https://github.com/embassy-rs/embassy", features = [
"defmt",
] }
panic-probe = { version = "0.3", features = ["print-defmt"] }
log = "0.4"
static_cell = { version = "1.1", features = ["nightly"] }
heapless = { version = "0.7.16", features = ["defmt", "serde"] }
embedded-io-async = { version = "0.5", features = ["defmt-03"] }
crc16 = "0.4"
dsmr5 = "0.3"
rust-mqtt = { version = "0.1.5", features = [
"defmt",
@@ -80,25 +78,28 @@ smoltcp = { version = "0.10.0", default-features = false, features = [
"dns-max-server-count-4",
] }
nourl = { version = "0.1.1", features = ["defmt"] }
reqwless = { version = "0.5.0", features = ["defmt"] }
embedded-storage = "0.3.0"
const_format = "0.2.31"
git-version = "0.3.5"
updater = { path = "../iot_tools/updater" }
[patch.crates-io]
embassy-executor = { git = "https://github.com/embassy-rs/embassy" }
embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy" }
embassy-rp = { git = "https://github.com/embassy-rs/embassy" }
embassy-time = { git = "https://github.com/embassy-rs/embassy" }
embassy-usb = { git = "https://github.com/embassy-rs/embassy" }
embassy-net = { git = "https://github.com/embassy-rs/embassy" }
embassy-sync = { git = "https://github.com/embassy-rs/embassy" }
embassy-futures = { git = "https://github.com/embassy-rs/embassy" }
embassy-boot-rp = { git = "https://github.com/embassy-rs/embassy" }
embassy-boot = { git = "https://github.com/embassy-rs/embassy" }
# Updated to embedded-io 0.5.0
rust-mqtt = { git = "https://git.huizinga.dev/Dreaded_X/rust-mqtt" }
# Make mqtt:// and mqtts:// actually work
nourl = { git = "https://git.huizinga.dev/Dreaded_X/nourl" }
# Waiting for this to get updated to embedded-io 0.5 properly
reqwless = { path = "../reqwless" }
[build-dependencies]
@@ -107,7 +108,8 @@ dotenvy = "0.15"
[features]
# With this feature enabled the cyw43 fiwmares are not included in the build and need to be manually flashed
# This helps speed up development
exclude_firmwares = []
include_firmwares = []
invert_rx = []
[profile.release]
debug = true

View File

@@ -1,24 +0,0 @@
[package]
name = "bootloader"
version = "0.1.0"
edition = "2021"
[dependencies]
defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true }
embassy-rp = { version = "0.1", features = ["nightly"] }
embassy-boot-rp = { version = "0.1" }
embassy-sync = { version = "0.2" }
embassy-time = { version = "0.1", features = ["nightly"] }
cortex-m = { version = "0.7", features = [
"inline-asm",
"critical-section-single-core",
] }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"
[features]
defmt = ["dep:defmt", "embassy-boot-rp/defmt", "embassy-rp/defmt"]
debug = ["defmt-rtt", "defmt"]

View File

@@ -1,36 +0,0 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
// By default cortex-m-rt expects memory.x, however this causes issues with workspaces as it
// will pick the first file that is found.
// In order to get around this we make a dummy memory.x file
File::create(out.join("memory.x")).unwrap();
// Use memory.x.in as a template for the actual memory.x
let memory = include_str!("../memory.x.in")
.replace("{BOOTLOADER}", "FLASH")
.replace("{ACTIVE}", "ACTIVE");
// And then include it with a unique name
File::create(out.join("memory_boot.x"))
.unwrap()
.write_all(memory.as_bytes())
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=../memory.x.in");
// And link with that one
println!("cargo:rustc-link-arg-bins=-Tmemory_boot.x");
println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
println!("cargo:rustc-link-arg-bins=-Tlink-rp.x");
if env::var("CARGO_FEATURE_DEFMT").is_ok() {
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
}
}

View File

@@ -1,46 +0,0 @@
#![no_std]
#![no_main]
use core::cell::RefCell;
use cortex_m_rt::{entry, exception};
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embassy_boot_rp::*;
use embassy_rp::flash::{Blocking, Flash};
use embassy_sync::blocking_mutex::Mutex;
const FLASH_SIZE: usize = 2 * 1024 * 1024;
#[entry]
fn main() -> ! {
let p = embassy_rp::init(Default::default());
let flash = Flash::<_, Blocking, FLASH_SIZE>::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let active_offset = config.active.offset();
let bl: BootLoader = BootLoader::prepare(config);
unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) }
}
#[no_mangle]
#[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
unsafe extern "C" fn HardFault() {
cortex_m::peripheral::SCB::sys_reset();
}
#[exception]
unsafe fn DefaultHandler(_: i16) -> ! {
const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32;
let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16;
panic!("DefaultHandler #{:?}", irqn);
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
cortex_m::asm::udf();
}

BIN
key.pub Normal file

Binary file not shown.

9
release.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
mkdir -p target/firmware
cargo objcopy --release --features=include_firmwares,invert_rx -- -O binary target/firmware/firmware
shasum -a 512 -b target/firmware/firmware | dd ibs=128 count=1 | xxd -p -r > target/firmware/checksum
signify -S -m target/firmware/checksum -s ~/Projects/crypt/R0/private/keys/firmware/pico_p1.sec -x target/firmware/checksum.sig
tail -n1 target/firmware/checksum.sig | base64 -d -i | dd ibs=10 skip=1 > target/firmware/signed
cat target/firmware/signed > target/firmware/firmware+signed
cat target/firmware/firmware >> target/firmware/firmware+signed

View File

@@ -2,11 +2,10 @@
#![no_main]
#![feature(type_alias_impl_trait)]
use core::cell::RefCell;
use core::{cell::RefCell, str::from_utf8};
use embassy_boot_rp::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig};
use embedded_storage::nor_flash::NorFlash;
use heapless::{String, Vec};
use heapless::Vec;
use rand::{
rngs::{SmallRng, StdRng},
RngCore, SeedableRng,
@@ -25,23 +24,18 @@ use embassy_rp::{
clocks::RoscRng,
flash::{Flash, WRITE_SIZE},
gpio::{Level, Output},
peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0, UART0},
peripherals::{DMA_CH1, PIN_23, PIN_25, PIO0, UART0},
pio::{self, Pio},
uart::{self, BufferedUartRx, Parity},
uart::{self, Async, Parity, UartRx},
};
use embassy_sync::{
blocking_mutex::{raw::NoopRawMutex, Mutex},
channel::{Channel, Sender},
};
use embassy_time::{Duration, Ticker, Timer};
use embedded_io_async::Read;
use dsmr5::Readout;
use nourl::Url;
use reqwless::{
request::{Method, Request, RequestBuilder},
response::Response,
};
use rust_mqtt::{
client::{
client::MqttClient,
@@ -49,16 +43,15 @@ use rust_mqtt::{
},
packet::v5::publish_packet::QualityOfService,
};
use serde::{Deserialize, Serialize};
use static_cell::make_static;
use const_format::formatcp;
use defmt::{debug, error, info, warn, Debug2Format};
use defmt::*;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
UART0_IRQ => uart::BufferedInterruptHandler<UART0>;
UART0_IRQ => uart::InterruptHandler<UART0>;
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
});
@@ -67,42 +60,16 @@ const TOPIC_BASE: &str = formatcp!("pico/{}", ID);
const TOPIC_STATUS: &str = formatcp!("{}/status", TOPIC_BASE);
const TOPIC_UPDATE: &str = formatcp!("{}/update", TOPIC_BASE);
const VERSION: &str = git_version::git_version!();
const PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("../key.pub");
#[derive(Deserialize)]
struct UpdateMessage<'a> {
url: &'a str,
}
impl UpdateMessage<'_> {
fn get_url(&self) -> String<1024> {
self.url.into()
}
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case", tag = "status")]
enum Status<'a> {
Connected { version: &'a str },
Disconnected,
PreparingUpdate,
Erasing,
Writing { progress: u32 },
UpdateComplete,
}
impl Status<'_> {
fn vec(&self) -> Vec<u8, 1024> {
serde_json_core::to_vec(self)
.expect("The buffer should be large enough to contain all the data")
}
}
const FLASH_SIZE: usize = 2 * 1024 * 1024;
#[embassy_executor::task]
async fn wifi_task(
runner: cyw43::Runner<
'static,
Output<'static, PIN_23>,
PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>,
PioSpi<'static, PIN_25, PIO0, 0, DMA_CH1>,
>,
) -> ! {
runner.run().await
@@ -113,14 +80,14 @@ async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! {
stack.run().await
}
async fn get_readout(rx: &mut BufferedUartRx<'static, UART0>) -> Readout {
async fn get_readout(rx: &mut UartRx<'static, UART0, Async>) -> Readout {
let mut buffer: Vec<u8, 2048> = Vec::new();
buffer.push(b'/').unwrap();
let mut byte = [0; 1];
debug!("Waiting for next telegram...");
loop {
rx.read_exact(&mut byte).await.unwrap();
rx.read(&mut byte).await.unwrap();
if byte[0] == b'/' {
break;
@@ -137,7 +104,7 @@ async fn get_readout(rx: &mut BufferedUartRx<'static, UART0>) -> Readout {
debug!("Start of CRC detected");
let mut crc = [0; 4];
rx.read_exact(&mut crc).await.unwrap();
rx.read(&mut crc).await.unwrap();
buffer.extend_from_slice(&crc).unwrap();
@@ -155,7 +122,7 @@ async fn get_readout(rx: &mut BufferedUartRx<'static, UART0>) -> Readout {
#[embassy_executor::task]
async fn uart_rx_task(
mut rx: BufferedUartRx<'static, UART0>,
mut rx: UartRx<'static, UART0, Async>,
sender: Sender<'static, NoopRawMutex, dsmr5::state::State, 1>,
) {
info!("Wating for serial data");
@@ -174,7 +141,7 @@ async fn uart_rx_task(
/// Get the cyw43 firmware blobs
///
/// # Safety
/// When building with `exclude_firmwares` make sure to flash the firmwares using the following
/// When building without `include_firmwares` make sure to flash the firmwares using the following
/// commands:
/// ```bash
/// probe-rs download firmware/43439A0.bin --format bin --chip RP2040 --base-address 0x101BE000
@@ -182,7 +149,12 @@ async fn uart_rx_task(
/// ```
unsafe fn get_firmware() -> (&'static [u8], &'static [u8]) {
cfg_if::cfg_if! {
if #[cfg(feature = "exclude_firmwares")] {
if #[cfg(feature = "include_firmwares")] {
let fw = include_bytes!("../firmware/43439A0.bin");
let clm = include_bytes!("../firmware/43439A0_clm.bin");
(fw, clm)
} else {
// TODO: It would be nice if it could automatically get the correct size
extern "C" {
#[link_name = "__fw_start"]
@@ -192,11 +164,6 @@ unsafe fn get_firmware() -> (&'static [u8], &'static [u8]) {
}
(&fw, &clm)
} else {
let fw = include_bytes!("../firmware/43439A0.bin");
let clm = include_bytes!("../firmware/43439A0_clm.bin");
(fw, clm)
}
}
}
@@ -208,13 +175,26 @@ async fn main(spawner: Spawner) {
let channel = make_static!(Channel::<NoopRawMutex, _, 1>::new());
// TODO: Ideally we use async flash
// This has issues with alignment right now
let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut aligned = AlignedBuffer([0; WRITE_SIZE]);
let updater = BlockingFirmwareUpdater::new(config, &mut aligned.0);
let mut updater = updater::Updater::new(updater, TOPIC_STATUS, VERSION, PUBLIC_SIGNING_KEY);
// === UART ===
let mut config = uart::Config::default();
config.parity = Parity::ParityNone;
// config.invert_rx = true;
#[cfg(feature = "invert_rx")]
{
config.invert_rx = true;
}
let buf = make_static!([0u8; 2048]);
let rx = BufferedUartRx::new_with_rts(p.UART0, Irqs, p.PIN_1, p.PIN_3, buf, config);
let rx = UartRx::new(p.UART0, p.PIN_17, Irqs, p.DMA_CH0, config);
spawner.spawn(uart_rx_task(rx, channel.sender())).unwrap();
@@ -229,7 +209,7 @@ async fn main(spawner: Spawner) {
cs,
p.PIN_24,
p.PIN_29,
p.DMA_CH0,
p.DMA_CH1,
);
let (fw, clm) = unsafe { get_firmware() };
@@ -250,8 +230,7 @@ async fn main(spawner: Spawner) {
// Use the Ring Oscillator of the RP2040 as a source of true randomness to seed the
// cryptographically secure PRNG
let mut rng_rosc = RoscRng;
let mut rng = StdRng::from_rng(&mut rng_rosc).unwrap();
let mut rng = StdRng::from_rng(&mut RoscRng).unwrap();
let stack = make_static!(Stack::new(
net_device,
@@ -280,7 +259,7 @@ async fn main(spawner: Spawner) {
info!("IP Address: {}", cfg.address.address());
let mut rx_buffer = [0; 1024];
let mut tx_buffer = [0; 4096];
let mut tx_buffer = [0; 1024];
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
// socket.set_timeout(Some(Duration::from_secs(10)));
@@ -300,19 +279,17 @@ async fn main(spawner: Spawner) {
MqttVersion::MQTTv5,
// Use fast and simple PRNG to generate packet identifiers, there is no need for this to be
// cryptographically secure
SmallRng::from_rng(&mut rng_rosc).unwrap(),
SmallRng::from_rng(&mut RoscRng).unwrap(),
);
config.add_username(env!("MQTT_USERNAME"));
config.add_password(env!("MQTT_PASSWORD"));
config.add_max_subscribe_qos(QualityOfService::QoS1);
config.add_client_id(ID);
// Leads to InsufficientBufferSize error
let msg: &Vec<_, 1024> = make_static!(Status::Disconnected.vec());
config.add_will(TOPIC_STATUS, &msg, true);
updater.add_will(&mut config);
let mut recv_buffer = [0; 1024];
let mut write_buffer = [0; 4096];
let mut write_buffer = [0; 1024];
let mut client =
MqttClient::<_, 5, _>::new(socket, &mut write_buffer, &mut recv_buffer, config);
@@ -321,25 +298,8 @@ async fn main(spawner: Spawner) {
client.connect_to_broker().await.unwrap();
info!("MQTT Connected!");
// TODO: Ideally we use async flash
// This has issues with alignment right now
let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let mut aligned = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0);
// We wait with marking as booted until everything is connected
updater.mark_booted().unwrap();
let status = Status::Connected { version: VERSION }.vec();
client
.send_message(TOPIC_STATUS, &status, QualityOfService::QoS1, true)
.await
.unwrap();
client.subscribe_to_topic(TOPIC_UPDATE).await.unwrap();
updater.ready(&mut client).await.unwrap();
// Turn LED off when connected
control.gpio_set(0, false).await;
@@ -368,26 +328,39 @@ async fn main(spawner: Spawner) {
.await
.unwrap();
}
Either3::Third(message) => {
let message = match message {
Ok(message) => message,
Err(err) => {
error!("Failed to receive MQTT message: {}", err);
continue;
}
};
let message = match serde_json_core::from_slice::<UpdateMessage>(message.1) {
Ok((message, _)) => message,
Either3::Third(Ok((TOPIC_UPDATE, url))) => {
let url: Vec<_, 256> = match Vec::from_slice(url) {
Ok(url) => url,
Err(_) => {
error!("Unable to parse update message");
error!("URL is longer then buffer size");
continue;
}
};
let url = match from_utf8(&url) {
Ok(url) => url,
Err(err) => {
error!("Url is not valid utf-8 string: {}", Display2Format(&err));
continue;
}
};
let url = match Url::parse(url) {
Ok(url) => url,
Err(err) => {
error!("Failed to parse url: {}", err);
continue;
}
};
let url = message.get_url();
let url = Url::parse(url.as_str()).unwrap();
attempt_update(stack, &mut updater, &mut client, url).await;
if let Err(err) = updater.update(url, stack, &mut rng, &mut client).await {
error!("Update failed: {}", err)
}
}
Either3::Third(Ok(_)) => {
warn!("No handler for mqtt message");
}
Either3::Third(Err(err)) => {
error!("Failed to receive MQTT message: {}", err);
continue;
}
}
}
@@ -405,99 +378,3 @@ async fn wait_for_config(
yield_now().await;
}
}
const FLASH_SIZE: usize = 2 * 1024 * 1024;
async fn attempt_update<T, const MAX_PROPERTIES: usize, R, F>(
stack: &'static Stack<cyw43::NetDriver<'static>>,
updater: &mut BlockingFirmwareUpdater<'_, F, F>,
client: &mut MqttClient<'_, T, MAX_PROPERTIES, R>,
url: Url<'_>,
) where
T: embedded_io_async::Write + embedded_io_async::Read,
R: rand::RngCore,
F: NorFlash,
{
info!("Preparing for OTA...");
let status = Status::PreparingUpdate.vec();
client
.send_message(TOPIC_STATUS, &status, QualityOfService::QoS1, false)
.await
.unwrap();
let ip = stack.dns_query(url.host(), DnsQueryType::A).await.unwrap()[0];
let mut rx_buffer = [0; 4096 * 2];
let mut tx_buffer = [0; 1024];
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
let addr = (ip, url.port_or_default());
debug!("Addr: {}", addr);
socket.connect(addr).await.unwrap();
debug!("Path: {}", url.path());
Request::get(url.path())
.build()
.write(&mut socket)
.await
.unwrap();
let mut headers = [0; 4096];
let resp = Response::read(&mut socket, Method::GET, &mut headers)
.await
.unwrap();
let mut body = resp.body().reader();
debug!("Erasing flash...");
let status = Status::Erasing.vec();
client
.send_message(TOPIC_STATUS, &status, QualityOfService::QoS1, false)
.await
.unwrap();
let writer = updater
.prepare_update()
.map_err(|e| warn!("E: {:?}", Debug2Format(&e)))
.unwrap();
debug!("Writing...");
let status = Status::Writing { progress: 0 }.vec();
client
.send_message(TOPIC_STATUS, &status, QualityOfService::QoS1, false)
.await
.unwrap();
let mut buffer = AlignedBuffer([0; 4096]);
let mut offset = 0;
while let Ok(read) = body.read(&mut buffer.0).await {
if read == 0 {
break;
}
debug!("Writing chunk: {}", read);
writer.write(offset, &buffer.0[..read]).unwrap();
offset += read as u32;
let status = Status::Writing { progress: offset }.vec();
client
.send_message(TOPIC_STATUS, &status, QualityOfService::QoS1, false)
.await
.unwrap();
}
debug!("Total size: {}", offset);
updater.mark_updated().unwrap();
let status = Status::UpdateComplete.vec();
client
.send_message(TOPIC_STATUS, &status, QualityOfService::QoS1, false)
.await
.unwrap();
client.disconnect().await.unwrap();
info!("Restarting in 5 seconds...");
Timer::after(Duration::from_secs(5)).await;
cortex_m::peripheral::SCB::sys_reset();
}