diff --git a/.cargo/config.toml b/.cargo/config.toml index ff90964..da9c2af 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,6 @@ [build] # Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3) +# you may need to change the toolchain in `rust-toolc #target = "xtensa-esp32-espidf" #target = "xtensa-esp32s2-espidf" #target = "xtensa-esp32s3-espidf" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..4b0eff9 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,66 @@ +ARG VARIANT=bookworm-slim +FROM debian:${VARIANT} +ENV DEBIAN_FRONTEND=noninteractive +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +# Arguments +ARG CONTAINER_USER=esp +ARG CONTAINER_GROUP=esp +ARG ESP_BOARD=all +ARG GITHUB_TOKEN + +# Install dependencies +RUN apt-get update \ + && apt-get install -y pkg-config curl gcc clang libudev-dev unzip xz-utils \ + git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 \ + && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts + +# Set users +RUN adduser --disabled-password --gecos "" ${CONTAINER_USER} +USER ${CONTAINER_USER} +WORKDIR /home/${CONTAINER_USER} + +# Install rustup +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \ + --default-toolchain none -y --profile minimal + +# Update envs +ENV PATH=${PATH}:/home/${CONTAINER_USER}/.cargo/bin + +# Install extra crates +RUN ARCH=$($HOME/.cargo/bin/rustup show | grep "Default host" | sed -e 's/.* //') && \ + curl -L "https://github.com/esp-rs/espup/releases/latest/download/espup-${ARCH}" -o "${HOME}/.cargo/bin/espup" && \ + chmod u+x "${HOME}/.cargo/bin/espup" && \ + curl -L "https://github.com/esp-rs/espflash/releases/latest/download/cargo-espflash-${ARCH}.zip" -o "${HOME}/.cargo/bin/cargo-espflash.zip" && \ + unzip "${HOME}/.cargo/bin/cargo-espflash.zip" -d "${HOME}/.cargo/bin/" && \ + rm "${HOME}/.cargo/bin/cargo-espflash.zip" && \ + chmod u+x "${HOME}/.cargo/bin/cargo-espflash" && \ + curl -L "https://github.com/esp-rs/espflash/releases/latest/download/espflash-${ARCH}.zip" -o "${HOME}/.cargo/bin/espflash.zip" && \ + unzip "${HOME}/.cargo/bin/espflash.zip" -d "${HOME}/.cargo/bin/" && \ + rm "${HOME}/.cargo/bin/espflash.zip" && \ + chmod u+x "${HOME}/.cargo/bin/espflash" && \ + curl -L "https://github.com/esp-rs/embuild/releases/latest/download/ldproxy-${ARCH}.zip" -o "${HOME}/.cargo/bin/ldproxy.zip" && \ + unzip "${HOME}/.cargo/bin/ldproxy.zip" -d "${HOME}/.cargo/bin/" && \ + rm "${HOME}/.cargo/bin/ldproxy.zip" && \ + chmod u+x "${HOME}/.cargo/bin/ldproxy" && \ + curl -L "https://github.com/esp-rs/esp-web-flash-server/releases/latest/download/web-flash-${ARCH}.zip" -o "${HOME}/.cargo/bin/web-flash.zip" && \ + unzip "${HOME}/.cargo/bin/web-flash.zip" -d "${HOME}/.cargo/bin/" && \ + rm "${HOME}/.cargo/bin/web-flash.zip" && \ + chmod u+x "${HOME}/.cargo/bin/web-flash" + +# Install Xtensa Rust +RUN if [ -n "${GITHUB_TOKEN}" ]; then export GITHUB_TOKEN=${GITHUB_TOKEN}; fi \ + && ${HOME}/.cargo/bin/espup install\ + --targets "${ESP_BOARD}" \ + --log-level debug \ + --export-file /home/${CONTAINER_USER}/export-esp.sh + +# Set default toolchain +RUN rustup default esp + + +# Activate ESP environment +RUN echo "source /home/${CONTAINER_USER}/export-esp.sh" >> ~/.bashrc + +CMD [ "/bin/bash" ] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7461047 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,45 @@ +{ + "name": "esp-ota", + // Select between image and build propieties to pull or build the image. + "image": "docker.io/espressif/idf-rust:esp32_latest", + // "build": { + // "dockerfile": "Dockerfile", + // "args": { + // "CONTAINER_USER": "esp", + // "CONTAINER_GROUP": "esp", + // "ESP_BOARD": "esp32" + // } + // }, + "settings": { + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "editor.formatOnType": true, + "lldb.executable": "/usr/bin/lldb", + "files.watcherExclude": { + "**/target/**": true + }, + "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.checkOnSave.allTargets": false, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + } + }, + "extensions": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "serayuzgur.crates", + "mutantdino.resourcemonitor", + "yzhang.markdown-all-in-one", + "ms-vscode.cpptools", + "actboy168.tasks", + "Wokwi.wokwi-vscode" + ], + "forwardPorts": [ + 3333, + 8000 + ], + "workspaceMount": "source=${localWorkspaceFolder},target=/home/esp/esp-ota,type=bind,consistency=cached", + "workspaceFolder": "/home/esp/esp-ota" + } + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 101a548..e188b86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target /Cargo.lock -/.embuild \ No newline at end of file +/.embuild +/.devcontainer +.vscode/ +*.bin \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index e9667f9..9da7f64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,6 @@ log = { version = "0.4", optional = true } [build-dependencies] embuild = "0.31.2" + +[dev-dependencies] +esp-idf-svc = "0.48.1" diff --git a/beta.bin b/beta.bin new file mode 100644 index 0000000..7af3c54 Binary files /dev/null and b/beta.bin differ diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..6a04e1d --- /dev/null +++ b/examples/README.md @@ -0,0 +1,36 @@ +# Examples + +The directory contains simple examples for OTA updates + +## beta + +This is the example app that will replace the running OTA app. Typically, any new image should contain OTA update logic. +This app should be built before any of the ota apps can use it. +It is better to build in release mode to minify the application. + +Build: +``` +RUSTFLAGS="-C strip=symbols -C debuginfo=0" cargo build --example beta --release +espflash save-image --chip ./target//release/examples/beta ./beta.bin +``` + +## ota_from_flash + +The app contains the update in it's flash memory at compile time. The `beta.bin` image in the ESP32 app image format must be available at compile time. + +Build: +``` +cargo build --example ota_from_flash --release +``` +Flash and monitor: +``` +espflash flash ./target//release/examples/ota_from_flash --partition_table ./examples/partitions_4MB.csv --monitor +``` + +## partitions_4MB + +A partition table that splits the two OTA partitions evenly. + +## partitions_factory.csv + +The default partition table shipped with the ESP32. _Not suitable for OTA._ \ No newline at end of file diff --git a/examples/beta.rs b/examples/beta.rs new file mode 100644 index 0000000..ff73d2c --- /dev/null +++ b/examples/beta.rs @@ -0,0 +1,14 @@ +extern crate esp_ota; +use log::*; + +const FIRMWARE_ID: &str = "beta"; + +fn main() { + esp_idf_svc::sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + info!("FIRMWARE ID : {}", FIRMWARE_ID); + + if FIRMWARE_ID == "beta" { + esp_ota::mark_app_valid(); + } +} diff --git a/examples/ota_from_flash.rs b/examples/ota_from_flash.rs new file mode 100644 index 0000000..0f1e2be --- /dev/null +++ b/examples/ota_from_flash.rs @@ -0,0 +1,35 @@ +extern crate esp_ota; +use log::*; + +const FIRMWARE_ID: &str = "alpha"; + +const NEW_APP: &[u8] = include_bytes!("../beta.bin"); + +fn main() { + esp_idf_svc::sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + + info!("FIRMWARE ID : {}", FIRMWARE_ID); + if FIRMWARE_ID == "alpha" { + let mut ota = esp_ota::OtaUpdate::begin().unwrap(); + // write app to flash + + for app_chunk in NEW_APP.chunks(4096) { + if let Err(err) = ota.write(app_chunk) { + error!("Failed to write chunk"); + break; + } + } + //validate the written app + match ota.finalize() { + Err(x) => { + error!("Failed to validate image."); + () + } + Ok(mut x) => { + x.set_as_boot_partition().unwrap(); + x.restart(); + } + }; + } +} diff --git a/examples/partitions_4MB.csv b/examples/partitions_4MB.csv new file mode 100644 index 0000000..426219e --- /dev/null +++ b/examples/partitions_4MB.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +ota_0, app, ota_0, 0x10000, 0x180000, +ota_1, app, ota_1, 0x190000, 0x180000, diff --git a/examples/partitions_factory.csv b/examples/partitions_factory.csv new file mode 100644 index 0000000..484da04 --- /dev/null +++ b/examples/partitions_factory.csv @@ -0,0 +1,5 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, \ No newline at end of file