diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 60c6ef1..894af11 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install packages
- run: sudo apt install libatk1.0-dev pkg-config libgtk-3-dev
+ run: sudo apt install libatk1.0-dev pkg-config libgtk-3-dev libudev-dev
- name: Install Rust
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index f3496ee..1cbb059 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install packages
- run: sudo apt install libatk1.0-dev pkg-config libgtk-3-dev
+ run: sudo apt install libatk1.0-dev pkg-config libgtk-3-dev libudev-dev
- name: Install Rust
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
@@ -36,6 +36,8 @@ jobs:
tar xzf xpack-openocd-0.12.0-4-linux-x64.tar.gz
mkdir linux
cp easy_flash_daplink linux/
+ cp -r configs linux/
+ cp -r wireless_stack linux/
mv xpack-openocd-0.12.0-4/bin/* linux/
mv xpack-openocd-0.12.0-4/openocd/scripts/ linux/
7z a linux.zip linux/*
@@ -80,6 +82,8 @@ jobs:
unzip xpack-openocd-0.12.0-4-win32-x64.zip
mkdir windows
cp easy_flash_daplink.exe windows/
+ cp -r configs windows/
+ cp -r wireless_stack windows/
mv xpack-openocd-0.12.0-4/bin/* windows/
mv xpack-openocd-0.12.0-4/openocd/scripts/ windows/
7z a windows.zip windows/*
diff --git a/Cargo.lock b/Cargo.lock
index efea398..a543574 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "ab_glyph"
@@ -913,8 +913,9 @@ dependencies = [
[[package]]
name = "easy_flash_daplink"
-version = "0.1.0"
+version = "0.2.0"
dependencies = [
+ "async-io",
"directories",
"iced",
"iced_aw",
@@ -922,6 +923,7 @@ dependencies = [
"rfd",
"serde",
"serde_json",
+ "serialport",
"sysinfo",
]
@@ -1720,6 +1722,16 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "io-kit-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b"
+dependencies = [
+ "core-foundation-sys",
+ "mach2",
+]
+
[[package]]
name = "itertools"
version = "0.13.0"
@@ -1855,6 +1867,26 @@ dependencies = [
"libc",
]
+[[package]]
+name = "libudev"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0"
+dependencies = [
+ "libc",
+ "libudev-sys",
+]
+
+[[package]]
+name = "libudev-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@@ -1889,6 +1921,15 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
+[[package]]
+name = "mach2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -2006,6 +2047,17 @@ dependencies = [
"jni-sys",
]
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+]
+
[[package]]
name = "nix"
version = "0.29.0"
@@ -2914,6 +2966,25 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serialport"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2daa7abb9b965493e3c8f4184c6f46435484ff2538a332b886788cf6768b927b"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "core-foundation 0.10.0",
+ "core-foundation-sys",
+ "io-kit-sys",
+ "libudev",
+ "mach2",
+ "nix 0.26.4",
+ "scopeguard",
+ "unescaper",
+ "winapi",
+]
+
[[package]]
name = "sha1"
version = "0.10.6"
@@ -3361,6 +3432,15 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "unescaper"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815"
+dependencies = [
+ "thiserror",
+]
+
[[package]]
name = "unicode-bidi"
version = "0.3.15"
@@ -4275,7 +4355,7 @@ dependencies = [
"futures-sink",
"futures-util",
"hex",
- "nix",
+ "nix 0.29.0",
"ordered-stream",
"rand",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
index 7ce27e9..a2e1c31 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,15 +1,17 @@
[package]
name = "easy_flash_daplink"
-version = "0.1.0"
+version = "0.2.0"
edition = "2021"
[dependencies]
+async-io = "2"
directories = "5.0.1"
iced = "0.13.1"
-iced_aw = { version = "0.11.0", default-features = false, features = ["grid", "number_input"] }
+iced_aw = { version = "0.11.0", default-features = false, features = ["grid", "number_input", "tab_bar"] }
iced_fonts = { version = "0.1.1", features = [] }
rfd = { version = "0.15.0", default-features = false, features = ["gtk3"] }
sysinfo = "0.31.4"
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.127"
+serialport = "4.7.1"
diff --git a/README.md b/README.md
index c762c0a..4e7c9c9 100644
--- a/README.md
+++ b/README.md
@@ -4,24 +4,30 @@ English - [Français](README_fr.md)
- [DapLink - EasyFlash](#daplink---easyflash)
- [Introduction](#introduction)
+ - [DapLink](#daplink)
+ - [Stack Wireless](#stack-wireless)
- [Requirements](#requirements)
- [:computer: System](#computer-system)
- [:floppy\_disk: Bootloader \& Firmware](#floppy_disk-bootloader--firmware)
- [Usage](#usage)
- - [:electric\_plug: Hardware](#electric_plug-hardware)
+ - [:electric\_plug: Probe wiring (DapLink)](#electric_plug-probe-wiring-daplink)
- [STeaMi](#steami)
- [STM32 Disco L475 IoTNode](#stm32-disco-l475-iotnode)
- [STM32 Nucleo WB55](#stm32-nucleo-wb55)
- [:computer: Software](#computer-software)
+ - [DapLink](#daplink-1)
+ - [Wireless Stack](#wireless-stack)
- [:crab: Run from sources](#crab-run-from-sources)
- [Test files](#test-files)
- [`test-l475.bin`](#test-l475bin)
- [`test-wb55.bin`](#test-wb55bin)
+ - [Stack details](#stack-details)
## Introduction
-This tools is for internal usage, we use it to load daplink on target (STM32L475, STM32WB55, ...), to replace ST-LINK.
+This tool is for internal use, we use it (1) to load daplink on certain targets (STM32L475, STM32WB55, ...), replacing ST-LINK and (2) to flash a wireless stack (BLE, Thread, Zigbee, ...) on the STM32WB5xxG co-processor.
+### DapLink
With OpenOCD, the program steps are :
1. Unlock the RDP of the STM32F103xB
2. Mass erase flash
@@ -29,6 +35,10 @@ With OpenOCD, the program steps are :
4. Send firmware
5. _(optionnal)_ Send test program
+### Stack Wireless
+The stack is flashed to the co-processor using the [FUS](https://wiki.st.com/stm32mcu/wiki/Connectivity:STM32WB_FUS) and a program called `operator`, which acts as a relay between high-level commands and the FUS. The `operator` is precompiled, but **is not** the official ST Microelectronics version, the sources are available [here](https://github.com/steamicc/codal-steami-samples/tree/main/samples/Peripherals/FUS_WS_Operator) ([https://github.com/steamicc/codal-steami-samples/tree/main/samples/Peripherals/FUS_WS_Operator](https://github.com/steamicc/codal-steami-samples/tree/main/samples/Peripherals/FUS_WS_Operator)).
+
+

_Appearance may vary depending on your OS configuration._
@@ -38,10 +48,6 @@ _Appearance may vary depending on your OS configuration._
### :computer: System
To avoid installing OpenOCD on your computer, we recommend using the pre-built version maintained by [XPack](https://github.com/xpack-dev-tools/openocd-xpack/releases/tag/v0.12.0-4). The tested and compatible version is `v0.12.0-4`.
-Once you've downloaded and decompressed the archive corresponding to your OS, move the files/folders as follows (the term _root folder_ refers to the folder where the `easy-flash-daplink-[version]-[os]` executable is located):
- - **All files** in the `bin` folder must be moved to the root folder.
- - The `scripts` folder in the `openocd` folder must be moved to the root folder
-
:bulb: The archives available in [releases](https://github.com/steamicc/DapLink-EasyFlash/releases) already contains all the files placed where they need to be.
:warning: **Mac OS X** is not yet supported, but PRs are welcome :wink:
@@ -66,7 +72,7 @@ Once you've downloaded and decompressed the archive corresponding to your OS, mo
:warning: For other targets, please go directly to the [DapLink](https://github.com/ARMmbed/DAPLink) GitHub
## Usage
-### :electric_plug: Hardware
+### :electric_plug: Probe wiring (DapLink)
To enable the program to flash the bootloader, firmware and then the test program, the board must be wired to a probe (ST-Link, Black magic probe, etc.), and connected to the computer (using a micro-USB cable).
:warning: **Connect the target** (STM32 Disco L475 IoTNode, STM32 Nucleo WB55, ...) to your computer **after** wiring and **connecting the probe** to your computer
@@ -84,7 +90,7 @@ To enable the program to flash the bootloader, firmware and then the test progra
### :computer: Software
The program offer a simple GUI interface to select the bootload and the firmware that will be flashed on the STM32F1x.
-
+### DapLink
1. Select files you downloaded from [requirements](#floppy_disk-bootloader--firmware)
1. The bootloader binary file to flash (e.g: `stm32f103xb_bl.bin`)
2. The firmware binary file to flash (e.g: `stm32f103xb_stm32l475vg_if.bin`)
@@ -93,14 +99,20 @@ The program offer a simple GUI interface to select the bootload and the firmware
3. Define the timeout mount point waiting (e.g: `10`), in seconds
4. Push the "Start" button.
+### Wireless Stack
+ 1. Select the board serial port
+ 2. Choose a stack. A description of the stacks is available [below](#stack-details)
+ 3. Press the “Start” button
+
:bulb: Pro tips: All inputs are saved for the next time you will open the tool !
### :crab: Run from sources
1. Install [rust](https://www.rust-lang.org/tools/install)
-2. Clone or download this repository `git clone https://github.com/steamicc/DapLink-EasyFlash.git`
-3. Enter in project folder `cd DapLink-EasyFlash`
-4. Run `cargo run` from the project root.
+2. Install `openocd`
+3. Clone or download this repository `git clone https://github.com/steamicc/DapLink-EasyFlash.git`
+4. Enter in project folder `cd DapLink-EasyFlash`
+5. Run `cargo run` from the project root.
## Test files
In the `test bin` folder, you can find some simple programs for targets.
@@ -111,4 +123,29 @@ It blinks the LEDs, `LD1` and `LD2`, in two different patterns.
### `test-wb55.bin`
Toggles between high and low states of pins `PC10` and `PC12`. (The video is the result on the STeaMi board).
-
\ No newline at end of file
+
+
+## Stack details
+| Firmware | Description | STM32WB5xxG (1M) | STM32WB5xxY (640K) | STM32WB5xxE (512K) | STM32WB5xxC (256K) |
+| --- | --- | --- | --- | --- | --- |
+| BLE HCI AdvScan | * To be used for advertising and scanning through HCI interface
* BT SIG Certification listing : [Declaration ID D042213 / QDID 160726](https://launchstudio.bluetooth.com/ListingDetails/120678)
* HCI Layer only mode, layers supported : Link Layer, HCI | ✅ | ✅ | ✅ | ✅ |
+| BLE LLD | * BLE LLD (Low Level Driver) Radio Transparent firmware
* To be used for direct access on BLE LLD features and API | ✅ | ✅ | ✅ | ✅ |
+| BLE Stack full | * BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676)
* Full BLE Stack, layers supported : Link Layer, HCI, L2CAP, ATT, SM, GAP and GATT database
* Following features are kept (based on Basic stack library compared to previous deliveries):
* GAP peripheral, central (Master up to 8 links/Slave up to 8 links/all combinations in between)
* GATT server, client
* Data length extension
* 2Mbit PHY / PHY update
* Privacy
* White list
* Legacy Pairing, LE secure connections
* Direct Test Mode
* HCI interface (full, like stm32wb5x\_BLE\_HCILayer\_fw.bin)
* Following features are removed:
* **L2Cap Connection - oriented channels support (IP over BLE enabler)**
* **Channel selection #2 (under application flag selection)**
* **Some HCI interface features (won’t be able to process through HCI interface)** | ✅ | ✅ | ✅ | ✅ |
+| BLE Stack full extended | * BT SIG Certification listing (1) : [Declaration ID D060553 / QDID 182505](https://launchstudio.bluetooth.com/ListingDetails/146231)
* BT SIG Certification listing (2) : [Declaration ID D063069 / QDID 201968](https://launchstudio.bluetooth.com/ListingDetails/170086)
* BT SIG Certification listing (3) : [Declaration ID D063070 / QDID 216169](https://launchstudio.bluetooth.com/ListingDetails/186628)
* Full BLE Stack extended, layers supported : Link Layer, HCI, L2CAP, ATT, SM, GAP and GATT database
* Following features are kept:
* GAP peripheral, central (Master up to 8 links/Slave up to 8 links/all combinations in between)
* GATT server, client
* Data length extension
* 2Mbit PHY / PHY update
* Privacy
* White list
* Legacy Pairing, LE secure connections
* HCI interface (full, like stm32wb5x\_BLE\_HCILayer\_fw.bin)
* Direct Test Mode
* L2CAP connection oriented channels support (IP over BLE enabler)
* Channel selection #2 (under application flag selection)
* BLE Extended advertising (under application SHCI\_C2\_BLE\_INIT\_OPTIONS\_EXT\_ADV flag selection with following limitations on currently supported configurations as (max sets number, max advertising data length) equal to \[(1,1650),(2,1650),(3,1650),(4,1035),(5,621),(6,414),(7,207),(8,207)\] such as both parameters are compliant with allocated Total memory computed with BLE\_EXT\_ADV\_BUFFER\_SIZE based on Max Extended advertising configuration.
* BLE GATT caching supported (certified BLE 5.3)
* BLE Enhanced ATT supported (certified BLE 5.3)
* **Warning**: To use this binary, it is necessary to adapt the scatter file in the BLE applications as:
* The RAM\_A shared range shall be reduced to memory range \[0x20030000:0x200307FF\]
* The Mail-box buffers(MB\_MEM1, MB\_MEM2) shall be located in RAM\_B shared defined in memory range \[0x20038000:0x2003A7FF\]
* The RAM\_B shared shall be added to Total\_RAM\_region | ✅ | ✅ | ✅ | ✅ |
+| BLE Stack light | * BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676)
* Full BLE Stack, layers supported : Link Layer, HCI, L2CAP, ATT, SM, GAP and GATT database
* Wireless Ble stack Light configuration – Slave Only
* Following features are kept:
* GAP peripheral only (LL Slave up to 4 links)
* GATT server
* Data length extension
* 2Mbit PHY / PHY update
* Privacy
* White list
* Legacy Pairing, LE secure connections
* Direct Test Mode
* HCI interface (reduced)
* Channel selection #2 \[CSA2\] feature added
* **Additional beacon**
* Following features are removed:
* BLE “Slave Only” stack implies that with this stack configuration, STM32WB is not able to scan and request a BLE connection.
* It will just advertise, and accept incoming connection request from other master devices (e.g. Smartphone).
* While with the “full feature” BLE stack, STM32WB5xx is able to support both master and slave roles on different links (with the limitation of max 8 links in parallel). | ✅ | ✅ | ✅ | ✅ |
+| BLE HCILayer | * BT SIG Certification listing : [Declaration ID D042213 / QDID 160726](https://launchstudio.bluetooth.com/ListingDetails/120678)
* HCI Layer only mode, layers supported : Link Layer, HCI with Direct Test Mode | ✅ | ✅ | ✅ | ✅ |
+| BLE HCILayer extended | * BT SIG Certification listing (1) : [Declaration ID D060553 / QDID 182505](https://launchstudio.bluetooth.com/ListingDetails/146231)
* BT SIG Certification listing (2) : [Declaration ID D063069 / QDID 201968](https://launchstudio.bluetooth.com/ListingDetails/170086)
* HCI Layer only mode extended, layers supported : Link Layer, HCI with Direct Test Mode
* BLE Extended advertising (under application SHCI\_C2\_BLE\_INIT\_OPTIONS\_EXT\_ADV flag selection with following limitations on currently supported configurations as (max sets number, max advertising data length) equal to \[(1,1650),(2,1650),(3,1650),(4,1035),(5,621),(6,414),(7,207),(8,207)\] such as both parameters are compliant with allocated Total memory computed with BLE\_EXT\_ADV\_BUFFER\_SIZE based on Max Extended advertising configuration. | ✅ | ✅ | ✅ | ✅ |
+| Thread FTD | * Full Thread Device v1.3 ready
* To be used for Leader / Router / End Device Thread role (full features excepting Border Router) | ✅ | ✅ | ✅ | ❌ |
+| Thread MTD | * Minimal Thread Device v1.3 ready
* To be used for End Device and Sleepy End Device Thread role | ✅ | ✅ | ✅ | ❌ |
+| Thread RCP | * OpenThread Radio Co-Processor (RCP)
* To be used for Thread Border Router setup.
* Application layer and OpenThread core on the host processor, minimal OpenThread MAC on the 802.15.4 SoC.
* Communication between the RCP and the host processor is managed by OpenThread Daemon through an UART interface over the Spinel protocol. | ✅ | ✅ | ✅ | ✅ |
+| BLE Thread static | * Static Concurrent Mode BLE Thread
* Supports Full BLE Stack and Full Thread Device v1.3 ready
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ❌ | ❌ |
+| BLE Thread dynamic | * Dynamic Concurrent Mode BLE Thread
* Supports Full BLE Stack and Full Thread Device v1.3 ready
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ❌ | ❌ |
+| Mac 802\_15\_4 | * MAC API is based on latest official [IEEE Std 802.15.4-2011](http://grouper.ieee.org/groups/802/15/pub/Download.html)
* To be used for MAC FFD and RFD devices | ✅ | ✅ | ✅ | ✅ |
+| Phy 802\_15\_4 | * 802.15.4 Features exposed on application side
* Reduced number of commands called from application side to manage 802.15.4 API
* Not a Transparent mode, 802.15.4 API not deployed on application side
* Can to used with STM32CubeMonitor-RF application or dedicated M4 Application. | ✅ | ✅ | ✅ | ✅ |
+| Zigbee RFD | * Zigbee Reduced Function Device
* Zigbee Compliant Platform ready
* To be used for End Device Zigbee role | ✅ | ✅ | ✅ | ❌ |
+| Zigbee FFD | * Zigbee Compliant Platform ready
* Supports Full Function Device (FFD) | ✅ | ✅ | ✅ | ❌ |
+| BLE Mac 802\_15\_4 | * Static Concurrent Mode BLE MAC 802.15.4.
* Supports Full BLE Stack and MAC 802.15.4 API based on latest official [IEEE Std 802.15.4-2011](http://grouper.ieee.org/groups/802/15/pub/Download.html)
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ✅ |
+| BLE Zigbee FFD static | * Static Concurrent Mode BLE Zigbee FFD.
* Supports Full BLE Stack and Zigbee FFD(Full Function Device) Compliant Platform ready.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
+| BLE Zigbee RFD static | * Static Concurrent Mode BLE Zigbee RFD.
* Supports Full BLE Stack and Zigbee RFD(Reduced Function Device) Compliant Platform ready.
* Optimized for Power consumption.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
+| BLE Zigbee FFD dynamic | * Dynamic Concurrent Mode BLE Zigbee FFD.
* Supports Full BLE Stack and Zigbee FFD(Full Function Device) Compliant Platform ready.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
+| BLE Zigbee RFD dynamic | * Dynamic Concurrent Mode BLE Zigbee RFD.
* Supports Full BLE Stack and Zigbee RFD(Reduced Function Device) Compliant Platform ready.
* Optimized for Power consumption.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
\ No newline at end of file
diff --git a/README_fr.md b/README_fr.md
index a6b836d..047924e 100644
--- a/README_fr.md
+++ b/README_fr.md
@@ -4,49 +4,53 @@
- [DapLink - EasyFlash](#daplink---easyflash)
- [Introduction](#introduction)
+ - [DapLink](#daplink)
+ - [Stack Wireless](#stack-wireless)
- [Dépendances](#dépendances)
- [:computer: Système](#computer-système)
- - [:floppy\_disk: Bootloader \& Firmware](#floppy_disk-bootloader--firmware)
+ - [:floppy\_disk: Bootloader \& Firmware (DapLink)](#floppy_disk-bootloader--firmware-daplink)
- [Utilisation](#utilisation)
- - [:electric\_plug: Hardware](#electric_plug-hardware)
- - [STM32 Disco L475 IoTNode](#stm32-disco-l475-iotnode)
+ - [:electric\_plug: Connexion de la probe (Chargement DapLink)](#electric_plug-connexion-de-la-probe-chargement-daplink)
- [STeaMi](#steami)
- - [STLink V2 (ou clones)](#stlink-v2-ou-clones)
+ - [STM32 Disco L475 IoTNode](#stm32-disco-l475-iotnode)
- [STM32 Nucleo WB55](#stm32-nucleo-wb55)
- [:computer: Software](#computer-software)
+ - [Pour DapLink](#pour-daplink)
+ - [Pour la stack wireless](#pour-la-stack-wireless)
- [:crab: Éxécuter à partir des sources](#crab-éxécuter-à-partir-des-sources)
- [Test files](#test-files)
- [`test-l475.bin`](#test-l475bin)
- [`test-wb55.bin`](#test-wb55bin)
+ - [Descriptif détaillé des stacks](#descriptif-détaillé-des-stacks)
## Introduction
-Cet outil est à usage interne, nous l'utilisons pour charger daplink sur certaine target (STM32L475, STM32WB55, ...), en remplacement de ST-LINK.
+Cet outil est à usage interne, nous l'utilisons pour (1) charger daplink sur certaine target (STM32L475, STM32WB55, ...), en remplacement de ST-LINK et (2) pour flasher une stack sans-fil (BLE, Thread, Zigbee, ...) sur le co-processeur du STM32WB5xxG.
-A l'aide d'OpenOCD, le programme effectue les opérations suivantes :
+### DapLink
+La procédure de chargement de DapLink est la suivante :
1. Dévérouillage du RDP sur STM32F103xB
2. Effecement total de la flash
3. Ecriture du bootloader
4. Tranfert du firmware
5. _(optionnel)_ Transfert du programme de test
+### Stack Wireless
+La stack est flashé sur le co-processeur à l'aide du [FUS](https://wiki.st.com/stm32mcu/wiki/Connectivity:STM32WB_FUS) et d'un programme appelé `operator`, ce dernier fait le relais entre des commandes haut niveau de l'`operator` et le FUS. L'`operator` est précompilé, mais **n'est pas** la version officiel de ST Microelectronics, les sources sont disponible à cette [adresse](https://github.com/steamicc/codal-steami-samples/tree/main/samples/Peripherals/FUS_WS_Operator) ([https://github.com/steamicc/codal-steami-samples/tree/main/samples/Peripherals/FUS_WS_Operator](https://github.com/steamicc/codal-steami-samples/tree/main/samples/Peripherals/FUS_WS_Operator)).
+

_L'apparance peut varier suivant la configuration de l'OS_
## Dépendances
### :computer: Système
-To avoid installing OpenOCD on your computer, we recommend using the pre-built version maintained by [XPack](https://github.com/xpack-dev-tools/openocd-xpack/releases/tag/v0.12.0-4). The tested and compatible version is `v0.12.0-4`.
-
-Once you've downloaded and decompressed the archive corresponding to your OS, move the files/folders as follows (the term _root folder_ refers to the folder where the `easy-flash-daplink-[version]-[os]` executable is located):
- - **All files** in the `bin` folder must be moved to the root folder.
- - The `scripts` folder in the `openocd` folder must be moved to the root folder
+Pour ne pas avoir à installer OpenOCD sur votre système, nous vous recommendons d'utiliser la version pré-compilée par [XPack](https://github.com/xpack-dev-tools/openocd-xpack/releases/tag/v0.12.0-4). La version testée est `v0.12.0-4`.
-:bulb: The archives available in [releases](https://github.com/steamicc/DapLink-EasyFlash/releases) already contains all the files placed where they need to be.
+:bulb: Une archive `zip` est disponible dans les [releases](https://github.com/steamicc/DapLink-EasyFlash/releases). Elle contient tous les fichiers et modifications nécessaires (dossiers déplacés).
-:warning: **Mac OS X** n'est pas supporter, mais nous ouvert aux contributions :wink:
+:warning: **Mac OS X** n'est actuellement pas supporté (ni testé), mais nous ouvert aux contributions :wink:
-### :floppy_disk: Bootloader & Firmware
+### :floppy_disk: Bootloader & Firmware (DapLink)
- Bootloader
- STM32F103xB: [https://github.com/letssteam/DAPLink/releases/latest/download/stm32f103xb_bl.bin](https://github.com/letssteam/DAPLink/releases/latest/download/stm32f103xb_bl.bin)
@@ -67,17 +71,15 @@ Once you've downloaded and decompressed the archive corresponding to your OS, mo
## Utilisation
-### :electric_plug: Hardware
+### :electric_plug: Connexion de la probe (Chargement DapLink)
Pour permettre au programme de flasher le bootloader, le firmware, puis le programme de test, il est nécéssaire de cabler la carte à une probe (ST-Link, Black magic probe, ...), en plus de la connecter à l'ordinateur (à l'aide d'un câble micro-USB).
:warning: **Connecter la carte** (STM32 Disco L475 IoTNode, STM32 Nucleo WB55, ...) à votre ordinateur **après** avoir cablé et **connecté la probe**.
-#### STM32 Disco L475 IoTNode
-
#### STeaMi

-##### STLink V2 (ou clones)
+##### STM32 Disco L475 IoTNode
")
#### STM32 Nucleo WB55
@@ -86,6 +88,7 @@ Pour permettre au programme de flasher le bootloader, le firmware, puis le progr
### :computer: Software
Le programme offre une interface simplifiée, permettant de selectionner les fichiers qui seront utilisés.
+#### Pour DapLink
1. Utilisations des fichiers téléchargés à l'étape [Dépendance](#floppy_disk-bootloader--firmware)
1. Dans le premier champs sélectionner le bootloader (e.g: `stm32f103xb_bl.bin`)
2. Dans le second champs sélectionner le firmware (e.g: `stm32f103xb_stm32l475vg_if.bin`)
@@ -94,14 +97,21 @@ Le programme offre une interface simplifiée, permettant de selectionner les fic
3. Indiquer le temps d'attente maximal des périphérique de stockage (e.g: `10`), en secondes
4. Appuyer sur le bouton "Start"
+#### Pour la stack wireless
+ 1. Sélectionner le port série de votre carte
+ 2. Choisir une stack. Un descriptif des stacks est disponible [plus bas](#descriptif-détaillé-des-stacks)
+ 3. Appuyer sur le bouton "Start"
+
+
:bulb: Toutes les valeurs de champs sont sauvegarder lorsque vous quitter le programme.
### :crab: Éxécuter à partir des sources
1. Installer [rust](https://www.rust-lang.org/tools/install)
-2. Cloner ou télécharger le repository `git clone https://github.com/steamicc/DapLink-EasyFlash.git`
-3. Entrer dans le dossier `cd DapLink-EasyFlash`
-4. Éxécuter la commande `cargo run` à la racine du projet.
+2. Installer `openocd`
+3. Cloner ou télécharger le repository `git clone https://github.com/steamicc/DapLink-EasyFlash.git`
+4. Entrer dans le dossier `cd DapLink-EasyFlash`
+5. Éxécuter la commande `cargo run` à la racine du projet.
## Test files
@@ -113,4 +123,29 @@ Clignotement des leds, `LD1` and `LD2`, suivant deux schémas.
### `test-wb55.bin`
Alterne les pins `PC10` et `PC12`. (La vidéo est le résultat sur la STeaMi).
-
\ No newline at end of file
+
+
+## Descriptif détaillé des stacks
+| Firmware | Description | STM32WB5xxG (1M) | STM32WB5xxY (640K) | STM32WB5xxE (512K) | STM32WB5xxC (256K) |
+| --- | --- | --- | --- | --- | --- |
+| BLE HCI AdvScan | * To be used for advertising and scanning through HCI interface
* BT SIG Certification listing : [Declaration ID D042213 / QDID 160726](https://launchstudio.bluetooth.com/ListingDetails/120678)
* HCI Layer only mode, layers supported : Link Layer, HCI | ✅ | ✅ | ✅ | ✅ |
+| BLE LLD | * BLE LLD (Low Level Driver) Radio Transparent firmware
* To be used for direct access on BLE LLD features and API | ✅ | ✅ | ✅ | ✅ |
+| BLE Stack full | * BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676)
* Full BLE Stack, layers supported : Link Layer, HCI, L2CAP, ATT, SM, GAP and GATT database
* Following features are kept (based on Basic stack library compared to previous deliveries):
* GAP peripheral, central (Master up to 8 links/Slave up to 8 links/all combinations in between)
* GATT server, client
* Data length extension
* 2Mbit PHY / PHY update
* Privacy
* White list
* Legacy Pairing, LE secure connections
* Direct Test Mode
* HCI interface (full, like stm32wb5x\_BLE\_HCILayer\_fw.bin)
* Following features are removed:
* **L2Cap Connection - oriented channels support (IP over BLE enabler)**
* **Channel selection #2 (under application flag selection)**
* **Some HCI interface features (won’t be able to process through HCI interface)** | ✅ | ✅ | ✅ | ✅ |
+| BLE Stack full extended | * BT SIG Certification listing (1) : [Declaration ID D060553 / QDID 182505](https://launchstudio.bluetooth.com/ListingDetails/146231)
* BT SIG Certification listing (2) : [Declaration ID D063069 / QDID 201968](https://launchstudio.bluetooth.com/ListingDetails/170086)
* BT SIG Certification listing (3) : [Declaration ID D063070 / QDID 216169](https://launchstudio.bluetooth.com/ListingDetails/186628)
* Full BLE Stack extended, layers supported : Link Layer, HCI, L2CAP, ATT, SM, GAP and GATT database
* Following features are kept:
* GAP peripheral, central (Master up to 8 links/Slave up to 8 links/all combinations in between)
* GATT server, client
* Data length extension
* 2Mbit PHY / PHY update
* Privacy
* White list
* Legacy Pairing, LE secure connections
* HCI interface (full, like stm32wb5x\_BLE\_HCILayer\_fw.bin)
* Direct Test Mode
* L2CAP connection oriented channels support (IP over BLE enabler)
* Channel selection #2 (under application flag selection)
* BLE Extended advertising (under application SHCI\_C2\_BLE\_INIT\_OPTIONS\_EXT\_ADV flag selection with following limitations on currently supported configurations as (max sets number, max advertising data length) equal to \[(1,1650),(2,1650),(3,1650),(4,1035),(5,621),(6,414),(7,207),(8,207)\] such as both parameters are compliant with allocated Total memory computed with BLE\_EXT\_ADV\_BUFFER\_SIZE based on Max Extended advertising configuration.
* BLE GATT caching supported (certified BLE 5.3)
* BLE Enhanced ATT supported (certified BLE 5.3)
* **Warning**: To use this binary, it is necessary to adapt the scatter file in the BLE applications as:
* The RAM\_A shared range shall be reduced to memory range \[0x20030000:0x200307FF\]
* The Mail-box buffers(MB\_MEM1, MB\_MEM2) shall be located in RAM\_B shared defined in memory range \[0x20038000:0x2003A7FF\]
* The RAM\_B shared shall be added to Total\_RAM\_region | ✅ | ✅ | ✅ | ✅ |
+| BLE Stack light | * BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676)
* Full BLE Stack, layers supported : Link Layer, HCI, L2CAP, ATT, SM, GAP and GATT database
* Wireless Ble stack Light configuration – Slave Only
* Following features are kept:
* GAP peripheral only (LL Slave up to 4 links)
* GATT server
* Data length extension
* 2Mbit PHY / PHY update
* Privacy
* White list
* Legacy Pairing, LE secure connections
* Direct Test Mode
* HCI interface (reduced)
* Channel selection #2 \[CSA2\] feature added
* **Additional beacon**
* Following features are removed:
* BLE “Slave Only” stack implies that with this stack configuration, STM32WB is not able to scan and request a BLE connection.
* It will just advertise, and accept incoming connection request from other master devices (e.g. Smartphone).
* While with the “full feature” BLE stack, STM32WB5xx is able to support both master and slave roles on different links (with the limitation of max 8 links in parallel). | ✅ | ✅ | ✅ | ✅ |
+| BLE HCILayer | * BT SIG Certification listing : [Declaration ID D042213 / QDID 160726](https://launchstudio.bluetooth.com/ListingDetails/120678)
* HCI Layer only mode, layers supported : Link Layer, HCI with Direct Test Mode | ✅ | ✅ | ✅ | ✅ |
+| BLE HCILayer extended | * BT SIG Certification listing (1) : [Declaration ID D060553 / QDID 182505](https://launchstudio.bluetooth.com/ListingDetails/146231)
* BT SIG Certification listing (2) : [Declaration ID D063069 / QDID 201968](https://launchstudio.bluetooth.com/ListingDetails/170086)
* HCI Layer only mode extended, layers supported : Link Layer, HCI with Direct Test Mode
* BLE Extended advertising (under application SHCI\_C2\_BLE\_INIT\_OPTIONS\_EXT\_ADV flag selection with following limitations on currently supported configurations as (max sets number, max advertising data length) equal to \[(1,1650),(2,1650),(3,1650),(4,1035),(5,621),(6,414),(7,207),(8,207)\] such as both parameters are compliant with allocated Total memory computed with BLE\_EXT\_ADV\_BUFFER\_SIZE based on Max Extended advertising configuration. | ✅ | ✅ | ✅ | ✅ |
+| Thread FTD | * Full Thread Device v1.3 ready
* To be used for Leader / Router / End Device Thread role (full features excepting Border Router) | ✅ | ✅ | ✅ | ❌ |
+| Thread MTD | * Minimal Thread Device v1.3 ready
* To be used for End Device and Sleepy End Device Thread role | ✅ | ✅ | ✅ | ❌ |
+| Thread RCP | * OpenThread Radio Co-Processor (RCP)
* To be used for Thread Border Router setup.
* Application layer and OpenThread core on the host processor, minimal OpenThread MAC on the 802.15.4 SoC.
* Communication between the RCP and the host processor is managed by OpenThread Daemon through an UART interface over the Spinel protocol. | ✅ | ✅ | ✅ | ✅ |
+| BLE Thread static | * Static Concurrent Mode BLE Thread
* Supports Full BLE Stack and Full Thread Device v1.3 ready
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ❌ | ❌ |
+| BLE Thread dynamic | * Dynamic Concurrent Mode BLE Thread
* Supports Full BLE Stack and Full Thread Device v1.3 ready
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ❌ | ❌ |
+| Mac 802\_15\_4 | * MAC API is based on latest official [IEEE Std 802.15.4-2011](http://grouper.ieee.org/groups/802/15/pub/Download.html)
* To be used for MAC FFD and RFD devices | ✅ | ✅ | ✅ | ✅ |
+| Phy 802\_15\_4 | * 802.15.4 Features exposed on application side
* Reduced number of commands called from application side to manage 802.15.4 API
* Not a Transparent mode, 802.15.4 API not deployed on application side
* Can to used with STM32CubeMonitor-RF application or dedicated M4 Application. | ✅ | ✅ | ✅ | ✅ |
+| Zigbee RFD | * Zigbee Reduced Function Device
* Zigbee Compliant Platform ready
* To be used for End Device Zigbee role | ✅ | ✅ | ✅ | ❌ |
+| Zigbee FFD | * Zigbee Compliant Platform ready
* Supports Full Function Device (FFD) | ✅ | ✅ | ✅ | ❌ |
+| BLE Mac 802\_15\_4 | * Static Concurrent Mode BLE MAC 802.15.4.
* Supports Full BLE Stack and MAC 802.15.4 API based on latest official [IEEE Std 802.15.4-2011](http://grouper.ieee.org/groups/802/15/pub/Download.html)
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ✅ |
+| BLE Zigbee FFD static | * Static Concurrent Mode BLE Zigbee FFD.
* Supports Full BLE Stack and Zigbee FFD(Full Function Device) Compliant Platform ready.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
+| BLE Zigbee RFD static | * Static Concurrent Mode BLE Zigbee RFD.
* Supports Full BLE Stack and Zigbee RFD(Reduced Function Device) Compliant Platform ready.
* Optimized for Power consumption.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
+| BLE Zigbee FFD dynamic | * Dynamic Concurrent Mode BLE Zigbee FFD.
* Supports Full BLE Stack and Zigbee FFD(Full Function Device) Compliant Platform ready.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
+| BLE Zigbee RFD dynamic | * Dynamic Concurrent Mode BLE Zigbee RFD.
* Supports Full BLE Stack and Zigbee RFD(Reduced Function Device) Compliant Platform ready.
* Optimized for Power consumption.
* BT SIG Certification listing : [Declaration ID D042164 / QDID 160724](https://launchstudio.bluetooth.com/ListingDetails/120676) | ✅ | ✅ | ✅ | ❌ |
\ No newline at end of file
diff --git a/configs/openocd-flash.cfg b/configs/f1x-flash.cfg
similarity index 100%
rename from configs/openocd-flash.cfg
rename to configs/f1x-flash.cfg
diff --git a/configs/openocd-mass-erase.cfg b/configs/f1x-mass-erase.cfg
similarity index 100%
rename from configs/openocd-mass-erase.cfg
rename to configs/f1x-mass-erase.cfg
diff --git a/configs/openocd-unlock.cfg b/configs/f1x-unlock.cfg
similarity index 100%
rename from configs/openocd-unlock.cfg
rename to configs/f1x-unlock.cfg
diff --git a/configs/wb5x.cfg b/configs/wb5x.cfg
new file mode 100644
index 0000000..73256c5
--- /dev/null
+++ b/configs/wb5x.cfg
@@ -0,0 +1,13 @@
+source [find interface/cmsis-dap.cfg]
+
+transport select swd
+
+set CHIPNAME STM32WB55RG
+set STOP_WATCHDOG 1
+set CLOCK_FREQ 4000
+
+reset_config none separate
+
+set CONNECT_UNDER_RESET 1
+
+source [find target/stm32wbx.cfg]
diff --git a/doc/screenshot.png b/doc/screenshot.png
index a3ebbfe..1b6b958 100644
Binary files a/doc/screenshot.png and b/doc/screenshot.png differ
diff --git a/src/dirs.rs b/src/dirs.rs
index 00c0a03..88768ee 100644
--- a/src/dirs.rs
+++ b/src/dirs.rs
@@ -1,7 +1,9 @@
-use std::{fs, path::PathBuf};
+use std::{fs, path::PathBuf, sync::OnceLock};
use directories::ProjectDirs;
+static EXE_DIR: OnceLock = OnceLock::new();
+
fn get_base_dir() -> Result {
match ProjectDirs::from("cc", "steami", "daplink-easyflash") {
Some(b) => Ok(b.data_dir().to_path_buf()),
@@ -11,18 +13,42 @@ fn get_base_dir() -> Result {
}
}
-pub fn get_script_dir() -> Result {
- let base = get_base_dir()?;
+pub fn set_exe_dir(pathbuf: PathBuf) {
+ let _ = EXE_DIR.set(pathbuf);
+}
- let script = base.join("scripts");
+pub fn get_exe_dir() -> Result {
+ EXE_DIR
+ .get()
+ .cloned()
+ .ok_or_else(|| "No base path available. This error should not be happen !".into())
+}
- if !script.exists() && fs::create_dir_all(&script).is_err() {
- match fs::create_dir_all(&script) {
- Ok(_) => (),
- Err(e) => {
- return Err(format!("Failed to create script directory {e}"));
- }
- }
+pub fn get_configs_dir() -> Result {
+ let base = get_exe_dir()?;
+
+ let script = base.join("configs");
+
+ if !script.exists() {
+ return Err(format!(
+ "The configs folder does not exist (exe_dir: {})",
+ base.to_str().unwrap_or("not defined")
+ ));
+ }
+
+ Ok(script)
+}
+
+pub fn get_wireless_stack_dir() -> Result {
+ let base = get_exe_dir()?;
+
+ let script = base.join("wireless_stack");
+
+ if !script.exists() {
+ return Err(format!(
+ "The wireless_stack folder does not exist (exe_dir: {})",
+ base.to_str().unwrap_or("not defined")
+ ));
}
Ok(script)
diff --git a/src/main.rs b/src/main.rs
index c685f84..1d9747b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,18 +1,20 @@
+use std::path::PathBuf;
use std::process::exit;
use iced::{window, Settings, Size};
use log_entries::LogEntries;
-use main_widget::EasyDapLink;
+use ui::main_window::MainWindow;
mod dirs;
mod disk_tool;
mod log_entries;
-mod log_widget;
-mod main_widget;
-mod messages;
mod open_ocd_task;
+mod operator_tool;
+mod stackfile_config;
mod utils;
+mod ui;
+
#[derive(Debug, Clone)]
struct ProcessResult {
pub code: Option,
@@ -20,16 +22,26 @@ struct ProcessResult {
}
fn main() -> iced::Result {
- match open_ocd_task::create_script_file() {
- Ok(_) => println!("Script created/updated"),
- Err(e) => {
- eprintln!("Failed to create/update scripts ({e})");
- exit(100);
- }
+ // Prefer CARGO_MANIFEST_DIR when running under `cargo run` so configs/
+ // and wireless_stack/ resolve from the repo root without manual symlinks.
+ // Falls back to the executable's parent dir for shipped builds.
+ let exe_dir = match std::env::var_os("CARGO_MANIFEST_DIR") {
+ Some(dir) => PathBuf::from(dir),
+ None => match std::env::current_exe() {
+ Ok(mut path) => {
+ path.pop();
+ path
+ }
+ Err(e) => {
+ eprintln!("Failed to determine executable directory ({e})");
+ exit(100);
+ }
+ },
};
+ dirs::set_exe_dir(exe_dir);
- iced::application(EasyDapLink::title, EasyDapLink::update, EasyDapLink::view)
- .theme(EasyDapLink::theme)
+ iced::application(MainWindow::title, MainWindow::update, MainWindow::view)
+ .theme(MainWindow::theme)
.settings(Settings::default())
.font(iced_fonts::REQUIRED_FONT_BYTES)
.window(window::Settings {
@@ -43,7 +55,7 @@ fn main() -> iced::Result {
}),
..Default::default()
})
- .subscription(EasyDapLink::application_subscription)
+ .subscription(MainWindow::application_subscription)
.exit_on_close_request(false)
.run()
}
diff --git a/src/messages.rs b/src/messages.rs
deleted file mode 100644
index 45e8968..0000000
--- a/src/messages.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use std::path::PathBuf;
-
-use iced::Event;
-
-use crate::ProcessResult;
-
-#[derive(Debug, Clone)]
-pub enum Message {
- BrowseBootloader,
- BrowseFirmware,
- BrowseUserFile,
- SelectBootloader(Option),
- SelectFirmware(Option),
- SelectUserFile(Option),
-
- InputBootloaderPath(String),
- InputFirmwarePath(String),
- InputUserFilePath(String),
-
- TimeoutChanged(u64),
- TargetNameChanged(String),
-
- StartProcess,
- DoneProcess,
- DoneEraseProcess(Result),
- DoneFlashProcess(Result),
- DoneUnlockProcess(Result),
-
- DoneWaitMaintenanceDisk(bool),
- DoneCopyFirmware(Result<(), String>),
- DoneWaitingDeviceDisk(bool),
- DoneCopyUserfile(Result<(), String>),
-
- ApplicationEvent(Event),
-}
diff --git a/src/open_ocd_task.rs b/src/open_ocd_task.rs
index 792d4ba..948e9a1 100644
--- a/src/open_ocd_task.rs
+++ b/src/open_ocd_task.rs
@@ -1,48 +1,51 @@
use std::{
+ collections::VecDeque,
fs,
io::{BufRead, BufReader},
path::{Path, PathBuf},
- process::{Command, Stdio},
+ process::{Command, ExitStatus, Stdio},
sync::{Arc, Mutex},
thread,
+ time::Duration,
};
+use async_io::Timer;
+use iced::futures::{channel::mpsc::Sender, SinkExt};
+
use crate::{
dirs,
log_entries::{LogEntries, LogType},
+ ui::messages::{TabDaplinkMessage, TabWsMessage, WithLogMessage},
ProcessResult,
};
-const UNLOCK_SCRIPT_FILENAME: &str = "openocd-unlock.cfg";
-const ERASE_SCRIPT_FILENAME: &str = "openocd-erase.cfg";
-const FLASH_SCRIPT_FILENAME: &str = "openocd-flash.cfg";
-
-const UNLOCK_SCRIPT: &str = include_str!("../configs/openocd-unlock.cfg");
-const ERASE_SCRIPT: &str = include_str!("../configs/openocd-mass-erase.cfg");
-const FLASH_SCRIPT: &str = include_str!("../configs/openocd-flash.cfg");
+pub const UNLOCK_SCRIPT_FILENAME: &str = "f1x-unlock.cfg";
+pub const ERASE_SCRIPT_FILENAME: &str = "f1x-mass-erase.cfg";
+pub const FLASH_SCRIPT_FILENAME: &str = "f1x-flash.cfg";
+pub const WB55_CONFIG: &str = "wb5x.cfg";
pub async fn unlock_target() -> Result {
- let script_folder: &Path = &dirs::get_script_dir()?;
+ let script_folder: &Path = &dirs::get_configs_dir()?;
let path_script = script_folder.join(UNLOCK_SCRIPT_FILENAME);
let mut command = Command::new("openocd");
command.args(&["-f", &format!("{}", path_script.to_str().unwrap())]);
- Ok(run_command(&mut command).await?)
+ Ok(run_command_sender::(&mut command, None).await?)
}
pub async fn erase_target() -> Result {
- let script_folder: &Path = &dirs::get_script_dir()?;
+ let script_folder: &Path = &dirs::get_configs_dir()?;
let path_script = script_folder.join(ERASE_SCRIPT_FILENAME);
let mut command = Command::new("openocd");
command.args(&["-f", &format!("{}", path_script.to_str().unwrap())]);
- Ok(run_command(&mut command).await?)
+ Ok(run_command_sender::(&mut command, None).await?)
}
pub async fn flash_target(bin_path: PathBuf) -> Result {
- let script_folder: &Path = &dirs::get_script_dir()?;
+ let script_folder: &Path = &dirs::get_configs_dir()?;
let path_script = script_folder.join(FLASH_SCRIPT_FILENAME);
if !bin_path.is_file() {
@@ -67,7 +70,7 @@ pub async fn flash_target(bin_path: PathBuf) -> Result {
&format!("{}", path_script.to_str().unwrap()),
]);
- Ok(run_command(&mut command).await?)
+ Ok(run_command_sender::(&mut command, None).await?)
}
pub fn is_installed() -> Result {
@@ -79,25 +82,55 @@ pub fn is_installed() -> Result {
Ok(child.status.success())
}
-pub fn create_script_file() -> Result<(), String> {
- let script_folder: &Path = &dirs::get_script_dir()?;
+pub async fn flash_wb55(
+ file: &str,
+ sender: &mut Sender,
+) -> Result {
+ let mut command = Command::new("openocd");
+ command.args(&[
+ "-f",
+ WB55_CONFIG,
+ "-c",
+ &format!("program {} verify reset", file),
+ "-c",
+ "reset run",
+ "-c",
+ "exit",
+ ]);
- let path_unlock = script_folder.join(UNLOCK_SCRIPT_FILENAME);
- let path_erase = script_folder.join(ERASE_SCRIPT_FILENAME);
- let path_flash = script_folder.join(FLASH_SCRIPT_FILENAME);
+ run_command_sender(&mut command, Some(sender)).await
+}
- fs::write(path_unlock, UNLOCK_SCRIPT)
- .and_then(move |_| fs::write(path_erase, ERASE_SCRIPT))
- .and_then(move |_| fs::write(path_flash, FLASH_SCRIPT))
- .map_err(|e| format!("{e}"))?;
+async fn run_command_sender(
+ cmd: &mut Command,
+ mut sender: Option<&mut Sender>,
+) -> Result
+where
+ MSG: WithLogMessage,
+{
+ let config_folder = dirs::get_configs_dir()?
+ .into_os_string()
+ .into_string()
+ .map_err(|_| "Failed to convert config path to string")?;
- Ok(())
-}
+ let ws_foler = dirs::get_wireless_stack_dir()?
+ .into_os_string()
+ .into_string()
+ .map_err(|_| "Failed to convert config path to string")?;
-async fn run_command(cmd: &mut Command) -> Result {
- let logs = LogEntries::default();
+ let tmp_folder = dirs::get_tmp_dir()?
+ .into_os_string()
+ .into_string()
+ .map_err(|_| "Failed to convert tmp_dir to string.")?;
- let cmd = cmd.args(&["-s", "scripts"]);
+ let cmd = cmd.args(&[
+ "-s",
+ &config_folder,
+ "-s",
+ &tmp_folder,
+ "-s",
+ &ws_foler,
+ ]);
let mut child = cmd
.stdout(Stdio::piped())
@@ -108,49 +141,74 @@ async fn run_command(cmd: &mut Command) -> Result {
let stderr = child.stderr.take().unwrap();
let stdout = child.stdout.take().unwrap();
- let mutex_log = Arc::new(Mutex::new(logs));
+ let mutex_messages: Arc>> = Arc::new(Mutex::new(VecDeque::new()));
- let thread_mutex = mutex_log.clone();
- let thread_stdout = thread::spawn(move || {
+ let thread_message = mutex_messages.clone();
+ let thread_stderr = thread::spawn(move || {
let lines = BufReader::new(stderr).lines();
for line in lines {
- match line {
- Ok(line) => {
- println!("[OPEN OCD] {line}");
- thread_mutex.lock().unwrap().push(LogType::Info(line));
- }
- Err(_) => (),
+ if let Ok(line) = line {
+ eprintln!("[OPEN OCD] {line}");
+ thread_message
+ .lock()
+ .unwrap()
+ .push_back(LogType::Info(format!(" {line}")));
}
}
});
- let thread_mutex = mutex_log.clone();
- let thread_stderr = thread::spawn(move || {
+ let thread_message = mutex_messages.clone();
+ let thread_stdout = thread::spawn(move || {
let lines = BufReader::new(stdout).lines();
for line in lines {
- match line {
- Ok(line) => {
- eprintln!("[OPEN OCD] {line}");
- thread_mutex.lock().unwrap().push(LogType::Info(line))
- }
- Err(_) => (),
+ if let Ok(line) = line {
+ println!("[OPEN OCD] {line}");
+ thread_message
+ .lock()
+ .unwrap()
+ .push_back(LogType::Info(format!(" {line}")));
}
}
});
- let output = child.wait().map_err(|e| e.to_string())?;
- let _ = thread_stdout.join();
- let _ = thread_stderr.join();
+ let logs = LogEntries::default();
+ let output: ExitStatus;
+ let mut tmp_deque: VecDeque = VecDeque::new();
- let logs = match Arc::into_inner(mutex_log) {
- Some(m) => match m.into_inner() {
- Ok(l) => l,
- Err(e) => return Err(format!("Unable to get inner mutex ({e})")),
- },
- None => return Err("Unable te get inner Arc".into()),
- };
+ loop {
+ if let Ok(mut deque) = mutex_messages.lock() {
+ tmp_deque = deque.clone();
+ deque.clear();
+ }
+
+ while let Some(msg) = tmp_deque.pop_front() {
+ if let Some(ref mut s) = sender {
+ let _ = s.send(MSG::log(msg)).await;
+ } else {
+ logs.push(msg);
+ }
+ }
+
+ if let Some(status) = child.try_wait().map_err(|e| e.to_string())? {
+ output = status;
+ let _ = thread_stdout.join();
+ let _ = thread_stderr.join();
+ break;
+ }
+
+ Timer::after(Duration::from_millis(50)).await;
+ }
+
+ if let Some(ref mut s) = sender {
+ let _ = s
+ .send(MSG::log(LogType::Warning(format!(
+ "Exit code: {}",
+ output.code().unwrap_or(i32::MIN)
+ ))))
+ .await;
+ }
Ok(ProcessResult {
code: output.code(),
diff --git a/src/operator_tool.rs b/src/operator_tool.rs
new file mode 100644
index 0000000..850a211
--- /dev/null
+++ b/src/operator_tool.rs
@@ -0,0 +1,48 @@
+use serde::Deserialize;
+
+#[allow(unused)]
+#[derive(Debug, Default, Clone, Deserialize)]
+pub struct OperatorVersionResult {
+ pub status: u32,
+ pub fus_version: u32,
+ pub copro_fw_version: String,
+ pub ws_version: u32,
+}
+
+#[derive(Debug, Default, Clone, Deserialize)]
+pub struct OperatorResult {
+ pub status: u32,
+ pub error: Option,
+}
+
+pub fn operator_error_string(error_code: u32) -> &'static str {
+ match error_code{
+ 0x00 => "FUS_STATE_NO_ERROR => No error occurred.",
+ 0x01 => "FUS_STATE_IMG_NOT_FOUND => Firmware/FUS upgrade requested but no image found. (such as image header corrupted or flash memory corrupted)",
+ 0x02 => "FUS_STATE_IMC_CORRUPT => Firmware/FUS upgrade requested, image found, authentic but not integer (corruption on the data)",
+ 0x03 => "FUS_STATE_IMG_NOT_AUTHENTIC => Firmware/FUS upgrade requested, image found, but its signature is not valid (wrong signature, wrong signature header)",
+ 0x04 => "FUS_STATE_NO_ENOUGH_SPACE => Firmware/FUS upgrade requested, image found and authentic, but there is no enough space to install it due to the already installed image. Install the stack in a lower location then try again.",
+ 0x05 => "FUS_IMAGE_USRABORT => Operation aborted by user or power off occurred",
+ 0x06 => "FUS_IMAGE_ERSERROR => Flash Erase Error",
+ 0x07 => "FUS_IMAGE_WRTERROR => Flash Write Error",
+ 0x08 => "FUS_AUTH_TAG_ST_NOTFOUND => STMicroelectronics Authentication tag not found error in the image",
+ 0x09 => "FUS_AUTH_TAG_CUST_NOTFOUND => Customer Authentication tag not found in the image",
+ 0x0A => "FUS_AUTH_KEY_LOCKED => The key that the user tries to load is currently locked",
+ 0x11 => "FUS_FW_ROLLBACK_ERROR",
+ _ => "Unknown code"
+ }
+}
+
+pub fn upgrade_status_string(status_code: u32) -> &'static str {
+ if status_code >= 0x30 {
+ "FUS_STATE_SERVICE_ONGOING"
+ } else if status_code >= 0x20 {
+ "FUS_STATE_FUS_UPGRD_ONGOING"
+ } else if status_code >= 0x10 {
+ "FUS_STATE_FW_UPGRD_ONGOING"
+ } else if status_code == 0x00 {
+ "FUS_STATE_IDLE"
+ } else {
+ "Unknown status code"
+ }
+}
diff --git a/src/stackfile_config.rs b/src/stackfile_config.rs
new file mode 100644
index 0000000..6db9096
--- /dev/null
+++ b/src/stackfile_config.rs
@@ -0,0 +1,94 @@
+use serde::{Deserialize, Serialize};
+
+#[allow(unused)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
+pub enum WirelessStackFile {
+ BleHciAdvScan,
+ #[default]
+ BleHciExt,
+ BleHci,
+ BleMac,
+ BleLld,
+ BleStackFullExt,
+ BleStackFull,
+ BleStackLight,
+ BleThreadDyn,
+ BleThreadSta,
+ BleZigbeeFfdDyn,
+ BleZigbeeFfdSta,
+ BleZigbeeRfdDyn,
+ BleZigbeeRfdSta,
+ Mac802154,
+ Phy802154,
+ ThreadFtd,
+ ThreadMtd,
+ ThreadRcp,
+ ZigbeeFfd,
+ ZigbeeRfd,
+}
+
+pub enum FusFile {
+ FusFor0_5_3,
+ Fus1_2_0,
+}
+
+pub const fn fus_config(file: FusFile) -> &'static str {
+ match file {
+ FusFile::FusFor0_5_3 => "stm32wb5xxG_FUS_fw_for_fus_0_5_3.hex",
+ FusFile::Fus1_2_0 => "stm32wb5xxG_FUS_fw.hex",
+ }
+}
+
+pub const fn wireless_stack_config(file: WirelessStackFile) -> &'static str {
+ match file {
+ WirelessStackFile::BleHciExt => "stm32wb5xxG_BLE_HCILayer_extended_fw.hex",
+ WirelessStackFile::BleHci => "stm32wb5xxG_BLE_HCILayer_fw.hex",
+ WirelessStackFile::BleHciAdvScan => "stm32wb5xxG_BLE_HCI_AdvScan_fw.hex",
+ WirelessStackFile::BleLld => "stm32wb5xxG_BLE_LLD_fw.hex",
+ WirelessStackFile::BleMac => "stm32wb5xxG_BLE_Mac_802_15_4_fw.hex",
+ WirelessStackFile::BleStackFullExt => "stm32wb5xxG_BLE_Stack_full_extended_fw.hex",
+ WirelessStackFile::BleStackFull => "stm32wb5xxG_BLE_Stack_full_fw.hex",
+ WirelessStackFile::BleStackLight => "stm32wb5xxG_BLE_Stack_light_fw.hex",
+ WirelessStackFile::BleThreadDyn => "stm32wb5xxG_BLE_Thread_dynamic_fw.hex",
+ WirelessStackFile::BleThreadSta => "stm32wb5xxG_BLE_Thread_static_fw.hex",
+ WirelessStackFile::BleZigbeeFfdDyn => "stm32wb5xxG_BLE_Zigbee_FFD_dynamic_fw.hex",
+ WirelessStackFile::BleZigbeeFfdSta => "stm32wb5xxG_BLE_Zigbee_FFD_static_fw.hex",
+ WirelessStackFile::BleZigbeeRfdDyn => "stm32wb5xxG_BLE_Zigbee_RFD_dynamic_fw.hex",
+ WirelessStackFile::BleZigbeeRfdSta => "stm32wb5xxG_BLE_Zigbee_RFD_static_fw.hex",
+ WirelessStackFile::Mac802154 => "stm32wb5xxG_Mac_802_15_4_fw.hex",
+ WirelessStackFile::Phy802154 => "stm32wb5xxG_Phy_802_15_4_fw.hex",
+ WirelessStackFile::ThreadFtd => "stm32wb5xxG_Thread_FTD_fw.hex",
+ WirelessStackFile::ThreadMtd => "stm32wb5xxG_Thread_MTD_fw.hex",
+ WirelessStackFile::ThreadRcp => "stm32wb5xxG_Thread_RCP_fw.hex",
+ WirelessStackFile::ZigbeeFfd => "stm32wb5xxG_Zigbee_FFD_fw.hex",
+ WirelessStackFile::ZigbeeRfd => "stm32wb5xxG_Zigbee_RFD_fw.hex",
+ }
+}
+
+impl std::fmt::Display for WirelessStackFile {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(match self {
+ WirelessStackFile::BleHciAdvScan => "BLE HCI AdvScan",
+ WirelessStackFile::BleHciExt => "BLE HCI Layer extended",
+ WirelessStackFile::BleHci => "BLE HCI Layer",
+ WirelessStackFile::BleMac => "BLE Mac 802.15.4",
+ WirelessStackFile::BleLld => "BLE LLD",
+ WirelessStackFile::BleStackFullExt => "BLE Stack full extended",
+ WirelessStackFile::BleStackFull => "BLE Stack full",
+ WirelessStackFile::BleStackLight => "BLE Stack light",
+ WirelessStackFile::BleThreadDyn => "BLE Thread dynamic",
+ WirelessStackFile::BleThreadSta => "BLE Thread static",
+ WirelessStackFile::BleZigbeeFfdDyn => "BLE Zigbee FFD dynamic",
+ WirelessStackFile::BleZigbeeFfdSta => "BLE Zigbee FFD static",
+ WirelessStackFile::BleZigbeeRfdDyn => "BLE Zigbee RFD dynamic",
+ WirelessStackFile::BleZigbeeRfdSta => "BLE Zigbee RFD static",
+ WirelessStackFile::Mac802154 => "Mac 802.15.4",
+ WirelessStackFile::Phy802154 => "Phy 802.15.4",
+ WirelessStackFile::ThreadFtd => "Thread FTD",
+ WirelessStackFile::ThreadMtd => "Thread MTD",
+ WirelessStackFile::ThreadRcp => "Thread RCP",
+ WirelessStackFile::ZigbeeFfd => "Zigbee FFD",
+ WirelessStackFile::ZigbeeRfd => "Zigbee RFD",
+ })
+ }
+}
diff --git a/src/log_widget.rs b/src/ui/log_widget.rs
similarity index 95%
rename from src/log_widget.rs
rename to src/ui/log_widget.rs
index a4ffc8b..a033bb1 100644
--- a/src/log_widget.rs
+++ b/src/ui/log_widget.rs
@@ -3,10 +3,9 @@ use iced::{
Color, Element, Font, Length, Pixels,
};
-use crate::{
- log_entries::{LogEntries, LogType},
- messages::Message,
-};
+use crate::log_entries::{LogEntries, LogType};
+
+use super::messages::Message;
const TEXT_SIZE: u16 = 12;
diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs
new file mode 100644
index 0000000..1049f0d
--- /dev/null
+++ b/src/ui/main_window.rs
@@ -0,0 +1,115 @@
+use std::fs;
+
+use iced::{
+ advanced::graphics::futures::event, widget::column, Element, Event, Subscription, Task, Theme,
+};
+use iced_aw::{TabBar, TabLabel};
+use serde::{Deserialize, Serialize};
+
+use crate::dirs;
+
+use super::{messages::Message, tab_daplink::TabDaplink, tab_wireless_stack::TabWirelessStack};
+
+const DAPLINK_TAB: u16 = 0;
+const WIRELESS_STACK_TAB: u16 = 1;
+const SETTINGS_FILE: &str = "fields.json";
+
+#[derive(Default, Debug, Serialize, Deserialize)]
+pub struct MainWindow {
+ #[serde(skip)]
+ theme: Theme,
+ #[serde(skip)]
+ active_tab: u16,
+ tab_daplink: TabDaplink,
+ tab_ws: TabWirelessStack,
+}
+
+impl MainWindow {
+ pub fn title(&self) -> String {
+ "Easy Flash DAPLink".to_owned()
+ }
+
+ pub fn theme(&self) -> Theme {
+ self.theme.clone()
+ }
+
+ pub fn update(&mut self, message: Message) -> Task {
+ match message {
+ Message::DapLink(dp_message) => self.tab_daplink.update(dp_message),
+ Message::WirelessStack(ws_message) => self.tab_ws.update(ws_message),
+
+ Message::ApplicationEvent(event) => match event {
+ Event::Keyboard(_) | Event::Mouse(_) | Event::Touch(_) => Task::none(),
+ Event::Window(event) => match event {
+ iced::window::Event::Opened { .. } => {
+ match dirs::get_settings_dir() {
+ Ok(settings_dir) => {
+ let fields_file = settings_dir.join(SETTINGS_FILE);
+ match fs::read(fields_file) {
+ Ok(content) => match serde_json::from_slice::(&content) {
+ Ok(obj) => {
+ self.tab_daplink = obj.tab_daplink;
+ self.tab_ws = obj.tab_ws;
+ println!("Settings loaded !");
+ }
+ Err(e) => {
+ eprintln!("Failed to deserialize settings. Error: {e}");
+ }
+ },
+ Err(e) => eprintln!("Failed to open settings file ({e})"),
+ }
+ }
+ Err(e) => eprintln!("Failed to get settings dirs (Error: {e}"),
+ };
+ self.tab_ws.refresh_serial_ports();
+ return Task::none();
+ }
+ iced::window::Event::CloseRequested => {
+ match dirs::get_settings_dir() {
+ Ok(settings_dir) => {
+ let fields_file = settings_dir.join(SETTINGS_FILE);
+ match fs::write(
+ fields_file,
+ serde_json::to_string_pretty(&self).unwrap_or("{}".into()),
+ ) {
+ Ok(_) => println!("Settings successfully saved"),
+ Err(e) => eprintln!("Failed to save settings ({e})"),
+ }
+ }
+ Err(e) => eprintln!("Failed to get settings dirs (Error: {e}"),
+ };
+ return iced::window::get_latest().and_then(iced::window::close);
+ }
+ _ => Task::none(),
+ },
+ },
+ Message::TabBarSelected(tab_idx) => {
+ self.active_tab = tab_idx;
+ Task::none()
+ }
+ }
+ }
+
+ pub fn view(&self) -> Element {
+ let mut col = column![TabBar::new(Message::TabBarSelected)
+ .push(DAPLINK_TAB, TabLabel::Text("DapLink".into()))
+ .push(WIRELESS_STACK_TAB, TabLabel::Text("Wireless Stack".into()))
+ .padding(1)
+ .set_active_tab(&self.active_tab)];
+
+ col = match self.active_tab {
+ DAPLINK_TAB => col.push(self.tab_daplink.view()),
+ WIRELESS_STACK_TAB => col.push(self.tab_ws.view()),
+ _ => {
+ eprintln!("Invalid selected tab ({})", self.active_tab);
+ col
+ }
+ };
+
+ col.into()
+ }
+
+ pub fn application_subscription(&self) -> Subscription {
+ event::listen().map(Message::ApplicationEvent)
+ }
+}
diff --git a/src/ui/messages.rs b/src/ui/messages.rs
new file mode 100644
index 0000000..8734b4e
--- /dev/null
+++ b/src/ui/messages.rs
@@ -0,0 +1,77 @@
+use std::path::PathBuf;
+
+use iced::Event;
+
+use crate::{
+ log_entries::{LogEntries, LogType},
+ stackfile_config::WirelessStackFile,
+ ProcessResult,
+};
+
+use super::tab_wireless_stack::{FwStep, SerialPortInfo};
+
+pub trait WithLogMessage {
+ fn log(log: LogType) -> Self;
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ DapLink(TabDaplinkMessage),
+ WirelessStack(TabWsMessage),
+
+ TabBarSelected(u16),
+ ApplicationEvent(Event),
+}
+
+#[derive(Debug, Clone)]
+pub enum TabDaplinkMessage {
+ LogMessage(LogType),
+
+ BrowseBootloader,
+ BrowseFirmware,
+ BrowseUserFile,
+ SelectBootloader(Option),
+ SelectFirmware(Option),
+ SelectUserFile(Option),
+
+ InputBootloaderPath(String),
+ InputFirmwarePath(String),
+ InputUserFilePath(String),
+
+ TimeoutChanged(u64),
+ TargetNameChanged(String),
+
+ StartProcess,
+ DoneProcess,
+ DoneEraseProcess(Result),
+ DoneFlashProcess(Result),
+ DoneUnlockProcess(Result),
+
+ DoneWaitMaintenanceDisk(bool),
+ DoneCopyFirmware(Result<(), String>),
+ DoneWaitingDeviceDisk(bool),
+ DoneCopyUserfile(Result<(), String>),
+}
+
+#[derive(Debug, Clone)]
+pub enum TabWsMessage {
+ StackSelected(WirelessStackFile),
+ SerialSelected(SerialPortInfo),
+ SerialRefresh,
+
+ StepChange(FwStep),
+ LogMessage(LogType),
+ LogMessages(LogEntries),
+}
+
+impl WithLogMessage for TabWsMessage {
+ fn log(log: LogType) -> TabWsMessage {
+ TabWsMessage::LogMessage(log)
+ }
+}
+
+impl WithLogMessage for TabDaplinkMessage {
+ fn log(log: LogType) -> TabDaplinkMessage {
+ TabDaplinkMessage::LogMessage(log)
+ }
+}
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
new file mode 100644
index 0000000..35a8058
--- /dev/null
+++ b/src/ui/mod.rs
@@ -0,0 +1,5 @@
+pub mod log_widget;
+pub mod main_window;
+pub mod messages;
+pub mod tab_daplink;
+pub mod tab_wireless_stack;
diff --git a/src/main_widget.rs b/src/ui/tab_daplink.rs
similarity index 70%
rename from src/main_widget.rs
rename to src/ui/tab_daplink.rs
index 012aa0c..c630117 100644
--- a/src/main_widget.rs
+++ b/src/ui/tab_daplink.rs
@@ -1,17 +1,18 @@
-use std::{fs, path::PathBuf, str::FromStr, time::Duration};
+use std::{path::PathBuf, str::FromStr, time::Duration};
use iced::{
- advanced::graphics::futures::event,
alignment::Horizontal,
widget::{button, center, column, container, opaque, row, stack, text, text_input},
- Element, Event, Length, Subscription, Task, Theme,
+ Element, Length, Task, Theme,
};
use iced_aw::{grid, grid_row, number_input};
use serde::{Deserialize, Serialize};
-use crate::{
- dirs, disk_tool, log_entries::LogType, log_widget::LogWidget, messages::Message, open_ocd_task,
- utils,
+use crate::{disk_tool, log_entries::LogType, open_ocd_task, utils};
+
+use super::{
+ log_widget::LogWidget,
+ messages::{Message, TabDaplinkMessage},
};
const MAINTENANCE_DISK_NAME: &str = "MAINTENANCE";
@@ -23,9 +24,7 @@ fn default_target_waiting_time() -> u64 {
}
#[derive(Debug, Serialize, Deserialize)]
-pub struct EasyDapLink {
- #[serde(skip)]
- theme: Theme,
+pub struct TabDaplink {
#[serde(skip)]
is_readonly: bool,
bootloader_path: PathBuf,
@@ -38,18 +37,11 @@ pub struct EasyDapLink {
log_widget: LogWidget,
}
-impl EasyDapLink {
- pub fn title(&self) -> String {
- "Easy Flash DAPLink".to_owned()
- }
-
- pub fn theme(&self) -> Theme {
- self.theme.clone()
- }
-
- pub fn update(&mut self, message: Message) -> Task {
+impl TabDaplink {
+ pub fn update(&mut self, message: TabDaplinkMessage) -> Task {
match message {
- Message::BrowseBootloader => {
+ TabDaplinkMessage::LogMessage(log) => self.log_widget.push(log),
+ TabDaplinkMessage::BrowseBootloader => {
self.is_readonly = true;
return Task::perform(
@@ -58,18 +50,18 @@ impl EasyDapLink {
"Select Bootloader file",
false,
),
- Message::SelectBootloader,
+ |x| Message::DapLink(TabDaplinkMessage::SelectBootloader(x)),
);
}
- Message::BrowseFirmware => {
+ TabDaplinkMessage::BrowseFirmware => {
self.is_readonly = true;
return Task::perform(
utils::select_file(self.firmware_path.clone(), "Select Firmware file", false),
- Message::SelectFirmware,
+ |x| Message::DapLink(TabDaplinkMessage::SelectFirmware(x)),
);
}
- Message::BrowseUserFile => {
+ TabDaplinkMessage::BrowseUserFile => {
self.is_readonly = true;
return Task::perform(
utils::select_file(
@@ -77,11 +69,11 @@ impl EasyDapLink {
"Select user program file",
true,
),
- Message::SelectUserFile,
+ |x| Message::DapLink(TabDaplinkMessage::SelectUserFile(x)),
);
}
- Message::SelectBootloader(p) => {
+ TabDaplinkMessage::SelectBootloader(p) => {
match p {
Some(p) => self.bootloader_path = p,
None => (),
@@ -90,7 +82,7 @@ impl EasyDapLink {
self.is_readonly = false;
}
- Message::SelectFirmware(p) => {
+ TabDaplinkMessage::SelectFirmware(p) => {
match p {
Some(p) => self.firmware_path = p,
None => (),
@@ -98,7 +90,7 @@ impl EasyDapLink {
self.is_readonly = false;
}
- Message::SelectUserFile(p) => {
+ TabDaplinkMessage::SelectUserFile(p) => {
match p {
Some(p) => self.user_file_path = p,
None => (),
@@ -106,19 +98,23 @@ impl EasyDapLink {
self.is_readonly = false;
}
- Message::InputBootloaderPath(s) => {
+ TabDaplinkMessage::InputBootloaderPath(s) => {
self.bootloader_path = PathBuf::from_str(&s).unwrap()
}
- Message::InputFirmwarePath(s) => self.firmware_path = PathBuf::from_str(&s).unwrap(),
- Message::InputUserFilePath(s) => self.user_file_path = PathBuf::from_str(&s).unwrap(),
+ TabDaplinkMessage::InputFirmwarePath(s) => {
+ self.firmware_path = PathBuf::from_str(&s).unwrap()
+ }
+ TabDaplinkMessage::InputUserFilePath(s) => {
+ self.user_file_path = PathBuf::from_str(&s).unwrap()
+ }
- Message::TimeoutChanged(v) => {
+ TabDaplinkMessage::TimeoutChanged(v) => {
self.target_waiting_time = v.clamp(TIMEOUT_MIN, TIMEOUT_MAX);
}
- Message::TargetNameChanged(s) => self.target_name = s,
+ TabDaplinkMessage::TargetNameChanged(s) => self.target_name = s,
- Message::StartProcess => {
+ TabDaplinkMessage::StartProcess => {
if !self.validate_fields() {
return Task::none();
}
@@ -142,14 +138,16 @@ impl EasyDapLink {
self.log_widget.push(LogType::InfoNoPrefix("\n\n".into()));
self.log_widget.push(LogType::Info("Unlock target".into()));
self.is_readonly = true;
- return Task::perform(open_ocd_task::unlock_target(), Message::DoneUnlockProcess);
+ return Task::perform(open_ocd_task::unlock_target(), |x| {
+ Message::DapLink(TabDaplinkMessage::DoneUnlockProcess(x))
+ });
}
- Message::DoneProcess => {
+ TabDaplinkMessage::DoneProcess => {
self.is_readonly = false;
}
- Message::DoneUnlockProcess(result) => {
+ TabDaplinkMessage::DoneUnlockProcess(result) => {
if result.is_err() {
self.log_widget.push(LogType::Error(format!(
"Failed to run unlock process. Error: {}",
@@ -165,10 +163,9 @@ impl EasyDapLink {
if code == 0 {
self.log_widget.push(LogType::InfoNoPrefix("\n\n".into()));
self.log_widget.push(LogType::Info("Erase target".into()));
- return Task::perform(
- open_ocd_task::erase_target(),
- Message::DoneEraseProcess,
- );
+ return Task::perform(open_ocd_task::erase_target(), |x| {
+ Message::DapLink(TabDaplinkMessage::DoneEraseProcess(x))
+ });
} else {
self.log_widget
.push(LogType::Warning(format!("Exit code: {}", code)));
@@ -179,9 +176,9 @@ impl EasyDapLink {
.push(LogType::Warning("Process terminated by signal.".into())),
}
}
- return Task::done(Message::DoneProcess);
+ return Task::done(Message::DapLink(TabDaplinkMessage::DoneProcess));
}
- Message::DoneEraseProcess(result) => {
+ TabDaplinkMessage::DoneEraseProcess(result) => {
if result.is_err() {
self.log_widget.push(LogType::Error(format!(
"Failed to run erase process. Error: {}",
@@ -201,7 +198,7 @@ impl EasyDapLink {
return Task::perform(
open_ocd_task::flash_target(self.bootloader_path.clone()),
- Message::DoneFlashProcess,
+ |x| Message::DapLink(TabDaplinkMessage::DoneFlashProcess(x)),
);
} else {
self.log_widget
@@ -213,9 +210,9 @@ impl EasyDapLink {
.push(LogType::Warning("Process terminated by signal.".into())),
}
}
- return Task::done(Message::DoneProcess);
+ return Task::done(Message::DapLink(TabDaplinkMessage::DoneProcess));
}
- Message::DoneFlashProcess(result) => {
+ TabDaplinkMessage::DoneFlashProcess(result) => {
if result.is_err() {
self.log_widget.push(LogType::Error(format!(
"Failed to run erase process. Error: {}",
@@ -238,7 +235,11 @@ impl EasyDapLink {
MAINTENANCE_DISK_NAME.into(),
Duration::from_secs(self.target_waiting_time),
),
- Message::DoneWaitMaintenanceDisk,
+ |x| {
+ Message::DapLink(
+ TabDaplinkMessage::DoneWaitMaintenanceDisk(x),
+ )
+ },
);
} else {
self.log_widget
@@ -250,15 +251,15 @@ impl EasyDapLink {
.push(LogType::Warning("Process terminated by signal.".into())),
}
}
- return Task::done(Message::DoneProcess);
+ return Task::done(Message::DapLink(TabDaplinkMessage::DoneProcess));
}
- Message::DoneWaitMaintenanceDisk(is_found) => {
+ TabDaplinkMessage::DoneWaitMaintenanceDisk(is_found) => {
if !is_found {
self.log_widget.push(LogType::Error(format!(
"TIMEOUT : The device '{MAINTENANCE_DISK_NAME}' was not found."
)));
- return Task::done(Message::DoneProcess);
+ return Task::done(Message::DapLink(TabDaplinkMessage::DoneProcess));
}
self.log_widget.push(LogType::InfoNoPrefix("\n\n".into()));
@@ -270,10 +271,10 @@ impl EasyDapLink {
MAINTENANCE_DISK_NAME.into(),
self.firmware_path.clone(),
),
- Message::DoneCopyFirmware,
+ |x| Message::DapLink(TabDaplinkMessage::DoneCopyFirmware(x)),
);
}
- Message::DoneCopyFirmware(result) => {
+ TabDaplinkMessage::DoneCopyFirmware(result) => {
match result {
Ok(_) => {
if self.user_file_path.exists() && self.user_file_path.is_file() {
@@ -287,7 +288,7 @@ impl EasyDapLink {
self.target_name.clone(),
Duration::from_secs(self.target_waiting_time),
),
- Message::DoneWaitingDeviceDisk,
+ |x| Message::DapLink(TabDaplinkMessage::DoneWaitingDeviceDisk(x)),
);
} else {
self.log_widget
@@ -298,16 +299,16 @@ impl EasyDapLink {
.log_widget
.push(LogType::Error(format!("Copy failed ({e})"))),
}
- return Task::done(Message::DoneProcess);
+ return Task::done(Message::DapLink(TabDaplinkMessage::DoneProcess));
}
- Message::DoneWaitingDeviceDisk(is_found) => {
+ TabDaplinkMessage::DoneWaitingDeviceDisk(is_found) => {
if !is_found {
self.log_widget.push(LogType::Error(format!(
"TIMEOUT : The device '{}' was not found.",
self.target_name
)));
- return Task::done(Message::DoneProcess);
+ return Task::done(Message::DapLink(TabDaplinkMessage::DoneProcess));
}
self.log_widget.push(LogType::InfoNoPrefix("\n\n".into()));
@@ -320,11 +321,11 @@ impl EasyDapLink {
self.target_name.clone(),
self.user_file_path.clone(),
),
- Message::DoneCopyUserfile,
+ |x| Message::DapLink(TabDaplinkMessage::DoneCopyUserfile(x)),
);
}
- Message::DoneCopyUserfile(result) => {
+ TabDaplinkMessage::DoneCopyUserfile(result) => {
match result {
Ok(_) => (),
Err(e) => self
@@ -332,32 +333,7 @@ impl EasyDapLink {
.push(LogType::Error(format!("Copy failed ({e})"))),
}
self.log_widget.push(LogType::InfoNoPrefix("\n\n".into()));
- return Task::done(Message::DoneProcess);
- }
-
- Message::ApplicationEvent(event) => {
- match event {
- Event::Keyboard(_) | Event::Mouse(_) | Event::Touch(_) => (),
- Event::Window(event) => match event {
- iced::window::Event::CloseRequested => {
- match dirs::get_settings_dir() {
- Ok(settings_dir) => {
- let fields_file = settings_dir.join("fields.json");
- match fs::write(
- fields_file,
- serde_json::to_string_pretty(&self).unwrap_or("{}".into()),
- ) {
- Ok(_) => println!("Fields succesfully saved"),
- Err(e) => eprintln!("Failed to save fields ({e})"),
- }
- }
- Err(e) => eprintln!("Failed to get settings dirs (Error: {e}"),
- };
- return iced::window::get_latest().and_then(iced::window::close);
- }
- _ => (),
- },
- };
+ return Task::done(Message::DapLink(TabDaplinkMessage::DoneProcess));
}
}
@@ -373,9 +349,9 @@ impl EasyDapLink {
"Bootloader",
self.bootloader_path.to_str().unwrap_or_default()
)
- .on_input(Message::InputBootloaderPath)
+ .on_input(|s| Message::DapLink(TabDaplinkMessage::InputBootloaderPath(s)))
.width(Length::Fill),
- button("...").on_press(Message::BrowseBootloader)
+ button("...").on_press(Message::DapLink(TabDaplinkMessage::BrowseBootloader))
]
.spacing(8)
),
@@ -383,9 +359,9 @@ impl EasyDapLink {
"Firmware file",
row![
text_input("Firmware", self.firmware_path.to_str().unwrap_or_default())
- .on_input(Message::InputFirmwarePath)
+ .on_input(|s| Message::DapLink(TabDaplinkMessage::InputFirmwarePath(s)))
.width(Length::Fill),
- button("...").on_press(Message::BrowseFirmware)
+ button("...").on_press(Message::DapLink(TabDaplinkMessage::BrowseFirmware))
]
.spacing(8)
),
@@ -396,9 +372,9 @@ impl EasyDapLink {
"User program",
self.user_file_path.to_str().unwrap_or_default()
)
- .on_input(Message::InputUserFilePath)
+ .on_input(|s| Message::DapLink(TabDaplinkMessage::InputUserFilePath(s)))
.width(Length::Fill),
- button("...").on_press(Message::BrowseUserFile)
+ button("...").on_press(Message::DapLink(TabDaplinkMessage::BrowseUserFile))
]
.spacing(8)
),
@@ -413,16 +389,14 @@ impl EasyDapLink {
grid_row!(
"Target mount name",
text_input("STeaMi, DIS_L4IOT, ...", &self.target_name)
- .on_input(Message::TargetNameChanged)
+ .on_input(|s| Message::DapLink(TabDaplinkMessage::TargetNameChanged(s)))
.width(200),
),
grid_row!(
"Timeout (s) for mount points",
- number_input(
- self.target_waiting_time,
- TIMEOUT_MIN..=TIMEOUT_MAX,
- Message::TimeoutChanged
- )
+ number_input(self.target_waiting_time, TIMEOUT_MIN..=TIMEOUT_MAX, |x| {
+ Message::DapLink(TabDaplinkMessage::TimeoutChanged(x))
+ })
.step(1)
.width(Length::Fill)
),
@@ -440,7 +414,7 @@ impl EasyDapLink {
.align_x(Horizontal::Center),
)
.width(Length::Fill)
- .on_press(Message::StartProcess);
+ .on_press(Message::DapLink(TabDaplinkMessage::StartProcess));
let log_view = container(self.log_widget.view())
.height(Length::Fill)
@@ -468,10 +442,6 @@ impl EasyDapLink {
final_view.spacing(16).padding(8).into()
}
- pub fn application_subscription(&self) -> Subscription {
- event::listen().map(Message::ApplicationEvent)
- }
-
fn validate_fields(&mut self) -> bool {
if !self.bootloader_path.exists() {
self.log_widget.push(LogType::Error(
@@ -487,7 +457,8 @@ impl EasyDapLink {
return false;
}
- if !self.bootloader_path.exists() && !self.user_file_path.to_str().unwrap().is_empty() {
+ let user_path_set = self.user_file_path.as_os_str().len() > 0;
+ if user_path_set && !self.user_file_path.exists() {
self.log_widget.push(LogType::Warning(
"Invalide user file (no such file or directory).".to_owned(),
));
@@ -495,30 +466,11 @@ impl EasyDapLink {
true
}
-
- fn load_fields() -> Option {
- match dirs::get_settings_dir() {
- Ok(settings_dir) => {
- let fields_file = settings_dir.join("fields.json");
- match fs::read_to_string(fields_file) {
- Ok(str) => match serde_json::from_str(&str) {
- Ok(object) => return Some(object),
- Err(e) => eprintln!("Failed to load fields ({e})"),
- },
- Err(e) => eprintln!("Failed to read fields file ({e})"),
- }
- }
- Err(e) => eprintln!("Failed to get settings dirs (Error: {e}"),
- };
-
- None
- }
}
-impl Default for EasyDapLink {
+impl Default for TabDaplink {
fn default() -> Self {
- let mut object = Self {
- theme: Theme::default(),
+ Self {
is_readonly: false,
bootloader_path: PathBuf::default(),
firmware_path: PathBuf::default(),
@@ -526,19 +478,6 @@ impl Default for EasyDapLink {
target_waiting_time: 10,
target_name: String::default(),
log_widget: LogWidget::default(),
- };
-
- match Self::load_fields() {
- Some(saved) => {
- object.bootloader_path = saved.bootloader_path;
- object.firmware_path = saved.firmware_path;
- object.user_file_path = saved.user_file_path;
- object.target_name = saved.target_name;
- object.target_waiting_time = saved.target_waiting_time;
- }
- None => (),
}
-
- object
}
}
diff --git a/src/ui/tab_wireless_stack.rs b/src/ui/tab_wireless_stack.rs
new file mode 100644
index 0000000..336da03
--- /dev/null
+++ b/src/ui/tab_wireless_stack.rs
@@ -0,0 +1,837 @@
+use std::{
+ cmp::Ordering,
+ fs,
+ future::Future,
+ io::{self, Write},
+ path::{Path, PathBuf},
+ time::Duration,
+};
+
+use async_io::Timer;
+
+use iced::{
+ alignment::Horizontal,
+ futures::{
+ channel::mpsc::{self, Sender},
+ SinkExt,
+ },
+ stream::channel,
+ widget::{button, center, column, container, opaque, pick_list, row, stack, text},
+ Element, Length, Task, Theme,
+};
+use iced_aw::{grid, grid_row};
+use serde::{Deserialize, Serialize};
+use serialport::{SerialPort, SerialPortType};
+
+use crate::{
+ dirs,
+ log_entries::{LogEntries, LogType},
+ open_ocd_task,
+ operator_tool::{
+ operator_error_string, upgrade_status_string, OperatorResult, OperatorVersionResult,
+ },
+ stackfile_config::{fus_config, wireless_stack_config, FusFile, WirelessStackFile},
+};
+
+use super::{
+ log_widget::LogWidget,
+ messages::{Message, TabWsMessage},
+};
+
+const DELETE_CMD: &[u8] = "DELETE\n".as_bytes();
+const STATUS_CMD: &[u8] = "STATUS\n".as_bytes();
+const UPGRADE_CMD: &[u8] = "UPGRADE\n".as_bytes();
+const VERSION_CMD: &[u8] = "VERSION\n".as_bytes();
+
+#[derive(Debug, Default, Clone)]
+pub enum FwStep {
+ #[default]
+ Ready,
+ StartProcess,
+ StepFlashOperator,
+ StepUpgradeFUS,
+ StepFlashFUS(String),
+ StepDeleteFW,
+ StepFlashFW,
+}
+
+#[derive(Debug, Clone, PartialEq, Default)]
+pub struct SerialPortInfo {
+ port: String,
+ product: Option,
+}
+
+const MAX_FUS_UPGRADE_ATTEMPTS: u32 = 3;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct TabWirelessStack {
+ fw_selected: WirelessStackFile,
+ #[serde(skip)]
+ serial_available_port: Vec,
+ #[serde(skip)]
+ serial_selected: Option,
+ #[serde(skip)]
+ log: LogWidget,
+ #[serde(skip)]
+ is_readonly: bool,
+ #[serde(skip)]
+ fus_upgrade_attempts: u32,
+}
+
+const ALL_STACK: [WirelessStackFile; 21] = [
+ WirelessStackFile::BleHciAdvScan,
+ WirelessStackFile::BleHciExt,
+ WirelessStackFile::BleHci,
+ WirelessStackFile::BleLld,
+ WirelessStackFile::BleMac,
+ WirelessStackFile::BleStackFullExt,
+ WirelessStackFile::BleStackFull,
+ WirelessStackFile::BleStackLight,
+ WirelessStackFile::BleThreadDyn,
+ WirelessStackFile::BleThreadSta,
+ WirelessStackFile::BleZigbeeFfdDyn,
+ WirelessStackFile::BleZigbeeFfdSta,
+ WirelessStackFile::BleZigbeeRfdDyn,
+ WirelessStackFile::BleZigbeeRfdSta,
+ WirelessStackFile::Mac802154,
+ WirelessStackFile::Phy802154,
+ WirelessStackFile::ThreadFtd,
+ WirelessStackFile::ThreadMtd,
+ WirelessStackFile::ThreadRcp,
+ WirelessStackFile::ZigbeeFfd,
+ WirelessStackFile::ZigbeeRfd,
+];
+
+impl TabWirelessStack {
+ pub fn view(&self) -> Element {
+ let grid_fields = grid!(
+ grid_row!(
+ "Wireless Stack",
+ pick_list(&ALL_STACK[..], Some(&self.fw_selected), |x| {
+ Message::WirelessStack(TabWsMessage::StackSelected(x))
+ })
+ .width(Length::Fill)
+ ),
+ grid_row!(
+ "Serial port",
+ row![
+ pick_list(
+ &self.serial_available_port[..],
+ self.serial_selected.as_ref(),
+ |x| Message::WirelessStack(TabWsMessage::SerialSelected(x))
+ )
+ .width(Length::Fill),
+ button(text("Refresh"))
+ .on_press(Message::WirelessStack(TabWsMessage::SerialRefresh))
+ ]
+ .spacing(8)
+ ),
+ )
+ .width(Length::Fill)
+ .column_spacing(8)
+ .row_spacing(8)
+ .column_widths(&[Length::Shrink, Length::Fill]);
+
+ let start_button = button(
+ text("Start 🚀")
+ .shaping(text::Shaping::Advanced)
+ .width(Length::Fill)
+ .align_x(Horizontal::Center),
+ )
+ .on_press(Message::WirelessStack(TabWsMessage::StepChange(
+ FwStep::StartProcess,
+ )))
+ .width(Length::Fill);
+
+ let log = container(self.log.view())
+ .height(Length::Fill)
+ .width(Length::Fill)
+ .padding(8);
+
+ let main_col = column![grid_fields, start_button].padding(8).spacing(16);
+
+ let layout = if self.is_readonly {
+ column![
+ stack![
+ main_col,
+ opaque(center(text("")).style(|theme: &Theme| {
+ let mut bg = theme.palette().background;
+ bg.a = 0.8;
+ container::Style {
+ background: Some(bg.into()),
+ ..container::Style::default()
+ }
+ }))
+ ],
+ log
+ ]
+ } else {
+ column![main_col, log]
+ };
+
+ layout.into()
+ }
+
+ pub fn update(&mut self, message: TabWsMessage) -> Task {
+ match message {
+ TabWsMessage::StackSelected(file) => self.fw_selected = file,
+ TabWsMessage::SerialSelected(serial) => self.serial_selected = Some(serial),
+ TabWsMessage::SerialRefresh => {
+ self.refresh_serial_ports();
+ }
+ TabWsMessage::StepChange(next_step) => {
+ return match next_step {
+ FwStep::Ready => {
+ self.is_readonly = false;
+ Task::none()
+ }
+ FwStep::StartProcess => self.step_start_process(),
+ FwStep::StepFlashOperator => self.step_flash_operator(),
+ FwStep::StepUpgradeFUS => self.step_upgrade_fus(),
+ FwStep::StepFlashFUS(file) => self.step_flash_fus(file),
+ FwStep::StepDeleteFW => self.step_delete_fw(),
+ FwStep::StepFlashFW => self.step_flash_fw(),
+ };
+ }
+ TabWsMessage::LogMessage(log) => self.log.push(log),
+ TabWsMessage::LogMessages(entries) => self.log.from_log_entries(&entries),
+ }
+
+ Task::none()
+ }
+
+ fn step_start_process(&mut self) -> Task {
+ if self.serial_selected.is_none() {
+ self.log
+ .push(LogType::Error("Please select a serial port".into()));
+
+ return Task::none();
+ }
+
+ self.is_readonly = true;
+ self.fus_upgrade_attempts = 0;
+ self.log
+ .push(LogType::Info("Start flashing...".to_string()));
+
+ let serial = self.serial_selected.as_ref().unwrap().clone();
+ Self::message_runner(|mut o| async move {
+ match Self::test_serial_port(&serial.port) {
+ Ok(_) => Self::send_step(&mut o, FwStep::StepFlashOperator).await,
+ Err(e) => Self::error_handle(&mut o, e).await,
+ };
+ })
+ }
+
+ fn step_flash_operator(&mut self) -> Task {
+ self.log.push(LogType::Info("Flash operator".to_string()));
+
+ Self::message_runner(|mut o| async move {
+ match open_ocd_task::flash_wb55("wb55_operator.hex", &mut o).await {
+ Ok(result) => match result.code {
+ Some(0) => {
+ Self::send_logs(&mut o, result.log).await;
+ Self::send_step(&mut o, FwStep::StepUpgradeFUS).await;
+ }
+ Some(code) => {
+ Self::send_log(
+ &mut o,
+ LogType::Error(format!("Flash failed (exit code {code})")),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ }
+ None => {
+ Self::send_log(
+ &mut o,
+ LogType::Error(
+ "Flash failed: OpenOCD terminated by signal".into(),
+ ),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ }
+ },
+ Err(e) => Self::error_handle(&mut o, e).await,
+ }
+ })
+ }
+
+ fn step_upgrade_fus(&mut self) -> Task {
+ self.log.push(LogType::Info("FUS update".to_string()));
+
+ let serial = self.serial_selected.as_ref().unwrap().clone();
+ Self::message_runner(|mut o| async move {
+ Timer::after(Duration::from_secs(1)).await;
+
+ let mut port = match Self::open_port(&serial.port) {
+ Ok(p) => p,
+ Err(e) => {
+ Self::error_handle(&mut o, format!("Failed to open serial port. Error: {e}"))
+ .await;
+ return;
+ }
+ };
+
+ if let Err(e) = Self::send_double_status(&mut port, &mut o).await {
+ Self::error_handle(&mut o, e).await;
+ return;
+ }
+
+ let line = match Self::send_and_read_serial(&mut port, VERSION_CMD, None, None).await {
+ Ok(s) => s,
+ Err(e) => {
+ Self::error_handle(&mut o, e).await;
+ return;
+ }
+ };
+ let version = match Self::parse_result::(&line) {
+ Ok(obj) => obj,
+ Err(e) => {
+ Self::error_handle(&mut o, e).await;
+ return;
+ }
+ };
+
+ let major = (version.fus_version & 0xFF000000) >> 24;
+ let minor = (version.fus_version & 0x00FF0000) >> 16;
+
+ let mut fus: Option<&str> = None;
+
+ if major == 0x00 {
+ fus = Some(fus_config(FusFile::FusFor0_5_3));
+ } else if major == 0x01 && minor < 0x02 {
+ fus = Some(fus_config(FusFile::Fus1_2_0));
+ } else if major == 0x01 && minor == 0x02 {
+ Self::send_log(&mut o, LogType::Info("FUS is up to date".to_string())).await;
+ } else if major == 0x02 {
+ Self::send_log(
+ &mut o,
+ LogType::Warning(
+ "FUS is ahead ! Let's give it a try. But it could fail...".to_string(),
+ ),
+ )
+ .await;
+ } else {
+ Self::send_log(
+ &mut o,
+ LogType::Error("Unknown FUS version. Abort.".to_string()),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ return;
+ }
+
+ match fus {
+ Some(file) => Self::send_step(&mut o, FwStep::StepFlashFUS(file.to_string())).await,
+ None => Self::send_step(&mut o, FwStep::StepDeleteFW).await,
+ };
+ })
+ }
+
+ fn step_flash_fus(&mut self, file: String) -> Task {
+ self.fus_upgrade_attempts += 1;
+ if self.fus_upgrade_attempts > MAX_FUS_UPGRADE_ATTEMPTS {
+ self.log.push(LogType::Error(format!(
+ "FUS upgrade did not converge after {MAX_FUS_UPGRADE_ATTEMPTS} attempts. Aborting."
+ )));
+ self.is_readonly = false;
+ return Task::none();
+ }
+
+ self.log.push(LogType::Info(format!(
+ "Flash FUS (attempt {}/{})",
+ self.fus_upgrade_attempts, MAX_FUS_UPGRADE_ATTEMPTS
+ )));
+
+ let serial = self.serial_selected.as_ref().unwrap().clone();
+ Self::message_runner(|mut o| async move {
+ if let Err(e) = Self::prepare_merged_hex(&file) {
+ Self::error_handle(&mut o, e).await;
+ return;
+ }
+
+ match open_ocd_task::flash_wb55("merge.hex", &mut o).await {
+ Ok(result) => match result.code {
+ Some(0) => Self::send_logs(&mut o, result.log).await,
+ Some(code) => {
+ Self::send_log(
+ &mut o,
+ LogType::Error(format!("Flash failed (exit code {code})")),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ return;
+ }
+ None => {
+ Self::send_log(
+ &mut o,
+ LogType::Error(
+ "Flash failed: OpenOCD terminated by signal".into(),
+ ),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ return;
+ }
+ },
+ Err(e) => {
+ Self::error_handle(&mut o, e).await;
+ return;
+ }
+ }
+
+ Self::send_log(&mut o, LogType::Info("Send UPGRADE command".into())).await;
+ match Self::fus_upgrade_cmd(&serial, &mut o).await {
+ Ok(_) => Self::send_step(&mut o, FwStep::StepUpgradeFUS).await,
+ Err(e) => Self::error_handle(&mut o, e).await,
+ }
+ })
+ }
+
+ fn step_delete_fw(&mut self) -> Task {
+ self.log
+ .push(LogType::Info("Delete current wireless stack".to_string()));
+
+ let serial = self.serial_selected.as_ref().unwrap().clone();
+ Self::message_runner(|mut o| async move {
+ let mut port = match Self::open_port(&serial.port) {
+ Ok(port) => port,
+ Err(e) => {
+ Self::send_log(
+ &mut o,
+ LogType::Error(format!("Failed to open serial port. Error: {e}")),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ return;
+ }
+ };
+
+ let mut success = false;
+ for attempt in 0..3 {
+ Timer::after(Duration::from_secs(1)).await;
+ match Self::send_and_read_serial(&mut port, DELETE_CMD, None, None).await {
+ Ok(_) => {
+ success = true;
+ break;
+ }
+ Err(e) => {
+ Self::send_log(
+ &mut o,
+ LogType::Warning(format!(
+ "Delete attempt #{} failed. Error: {e}",
+ attempt + 1
+ )),
+ )
+ .await
+ }
+ }
+ }
+
+ if !success {
+ Self::send_log(
+ &mut o,
+ LogType::Error("Unable to send delete command.".to_string()),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ return;
+ }
+
+ match Self::send_double_status(&mut port, &mut o).await {
+ Ok(_) => Self::send_step(&mut o, FwStep::StepFlashFW).await,
+ Err(e) => Self::error_handle(&mut o, e).await,
+ };
+ })
+ }
+
+ fn step_flash_fw(&mut self) -> Task {
+ self.log
+ .push(LogType::Info("Flash wireless stack".to_string()));
+
+ let serial = self.serial_selected.as_ref().unwrap().clone();
+ let fw = wireless_stack_config(self.fw_selected);
+ Self::message_runner(move |mut o| async move {
+ if let Err(e) = Self::prepare_merged_hex(fw) {
+ Self::error_handle(&mut o, e).await;
+ return;
+ }
+
+ match open_ocd_task::flash_wb55("merge.hex", &mut o).await {
+ Ok(result) => match result.code {
+ Some(0) => Self::send_logs(&mut o, result.log).await,
+ Some(code) => {
+ Self::send_log(
+ &mut o,
+ LogType::Error(format!("Flash failed (exit code {code})")),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ return;
+ }
+ None => {
+ Self::send_log(
+ &mut o,
+ LogType::Error(
+ "Flash failed: OpenOCD terminated by signal".into(),
+ ),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ return;
+ }
+ },
+ Err(e) => {
+ Self::error_handle(&mut o, e).await;
+ return;
+ }
+ };
+
+ Self::send_log(&mut o, LogType::Info("Send UPGRADE command".into())).await;
+ match Self::fus_upgrade_cmd(&serial, &mut o).await {
+ Ok(_) => {
+ Self::send_log(
+ &mut o,
+ LogType::Info("Wireless stack is now flashed !".into()),
+ )
+ .await;
+ Self::send_step(&mut o, FwStep::Ready).await;
+ }
+ Err(e) => Self::error_handle(&mut o, e).await,
+ }
+ })
+ }
+
+ fn message_runner(f: impl FnOnce(mpsc::Sender) -> F + 'static) -> Task
+ where
+ F: Future