diff --git a/.cargo/config.toml b/.cargo/config.toml index d07ae7e..ff90964 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -29,7 +29,6 @@ build-std = ["std", "panic_abort"] [env] # Note: these variables are not used when using pio builder # Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF stable (v4.4) -#ESP_IDF_VERSION = { value = "branch:release/v4.4" } -# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF master (mainline) ESP_IDF_VERSION = { value = "branch:release/v4.4" } - +# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF master (mainline) +#ESP_IDF_VERSION = { value = "master" } diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..1e9a23e --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,45 @@ +ARG VARIANT=bullseye +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 TOOLCHAIN_VERSION=1.62.0.0 +ARG ESP_IDF_VERSION=release/v4.4 +ARG ESP_BOARD=esp32 +ARG INSTALL_RUST_TOOLCHAIN=install-rust-toolchain.sh + +# Install dependencies +RUN apt-get update \ + && apt-get install -y git curl gcc clang ninja-build libudev-dev unzip xz-utils\ + python3 python3-pip python3-venv libusb-1.0-0 libssl-dev pkg-config libtinfo5 libpython2.7 \ + && 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 Rust toolchain, extra crates and esp-idf +ENV PATH=${PATH}:/home/${CONTAINER_USER}/.cargo/bin:/home/${CONTAINER_USER}/opt/bin + +ADD --chown=${CONTAINER_USER}:${CONTAINER_GROUP} \ + https://github.com/esp-rs/rust-build/releases/download/v${TOOLCHAIN_VERSION}/${INSTALL_RUST_TOOLCHAIN} \ + /home/${CONTAINER_USER}/${INSTALL_RUST_TOOLCHAIN} + +RUN chmod a+x ${INSTALL_RUST_TOOLCHAIN} \ + && ./${INSTALL_RUST_TOOLCHAIN} \ + --extra-crates "ldproxy cargo-espflash wokwi-server web-flash" \ + --export-file /home/${CONTAINER_USER}/export-esp.sh \ + --esp-idf-version "${ESP_IDF_VERSION}" \ + --minified-esp-idf "YES" \ + --build-target "${ESP_BOARD}" \ + && rustup component add clippy rustfmt + +# 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..e7c2e35 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,45 @@ +{ + "name": "esp32_c3_rust_wifi_demo", + // Select between image and build propieties to pull or build the image. + // "image": "docker.io/espressif/idf-rust:esp32c3_v4.4_1.61.0.0", + "build": { + "dockerfile": "Dockerfile", + "args": { + "CONTAINER_USER": "esp", + "CONTAINER_GROUP": "esp", + "ESP_IDF_VERSION":"release/v4.4", + "ESP_BOARD": "esp32c3" + } + }, + "settings": { + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modifications", + "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", + "webfreak.debug", + "actboy168.tasks" + ], + "forwardPorts": [ + 9012, + 9333, + 8000 + ], + "workspaceMount": "source=${localWorkspaceFolder},target=/home/esp/esp32_c3_rust_wifi_demo,type=bind,consistency=cached", + "workspaceFolder": "/home/esp/esp32_c3_rust_wifi_demo" +} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +target diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 0000000..e271ca5 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,34 @@ +# Note: gitpod/workspace-base image references older version of CMake, it's necessary to install newer one +FROM gitpod/workspace-base +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +# ARGS +ARG CONTAINER_USER=gitpod +ARG CONTAINER_GROUP=gitpod +ARG TOOLCHAIN_VERSION=1.62.0.0 +ARG ESP_IDF_VERSION="release/v4.4" +ARG ESP_BOARD=esp32c3 +ARG INSTALL_RUST_TOOLCHAIN=install-rust-toolchain.sh + +# Install dependencies +RUN sudo install-packages git curl gcc ninja-build libudev-dev libpython2.7 \ + python3 python3-pip python3-venv libusb-1.0-0 libssl-dev pkg-config libtinfo5 clang +# Set User +USER ${CONTAINER_USER} +WORKDIR /home/${CONTAINER_USER} + +# Install Rust toolchain, extra crates and esp-idf +ENV PATH=${PATH}:/home/${CONTAINER_USER}/.cargo/bin:/home/${CONTAINER_USER}/opt/bin +ADD --chown=${CONTAINER_USER}:${CONTAINER_GROUP} \ + https://github.com/esp-rs/rust-build/releases/download/v${TOOLCHAIN_VERSION}/${INSTALL_RUST_TOOLCHAIN} \ + /home/${CONTAINER_USER}/${INSTALL_RUST_TOOLCHAIN} +RUN chmod a+x ${INSTALL_RUST_TOOLCHAIN} \ + && ./${INSTALL_RUST_TOOLCHAIN} \ + --extra-crates "ldproxy cargo-espflash wokwi-server web-flash" \ + --export-file /home/${CONTAINER_USER}/export-esp.sh \ + --esp-idf-version "${ESP_IDF_VERSION}" \ + --minified-esp-idf "YES" \ + --build-target "${ESP_BOARD}" \ + && rustup component add clippy rustfmt + diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..4f77344 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,23 @@ +image: + file: .gitpod.Dockerfile +tasks: + - name: Setup environment variables for Rust and ESP-IDF + command: | + source /home/gitpod/export-esp.sh +vscode: + extensions: + - matklad.rust-analyzer + - tamasfe.even-better-toml + - anwar.resourcemonitor + - yzhang.markdown-all-in-one + - webfreak.debug + - actboy168.tasks + - serayuzgur.crates +ports: + - port: 9012 + visibility: public + - port: 9333 + visibility: public + - port: 8000 + visibility: public + onOpen: open-browser \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index aa90533..494b084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] -authors = ["Ivan Li "] -edition = "2018" name = "ups-esp32c3-rust" +version = "0.2.0" +authors = ["Ivan Li "] +edition = "2021" resolver = "2" -version = "0.1.0" [profile.release] opt-level = "s" @@ -17,18 +17,17 @@ pio = ["esp-idf-sys/pio"] [dependencies] anyhow = "1" -embedded-graphics = "0.7.1" -embedded-hal = "1.0.0-alpha.8" -embedded-hal-0-2 = {package = "embedded-hal", version = "0.2.7", features = ["unproven"]} embedded-svc = "0.22.0" env_logger = "0.9.0" -esp-idf-hal = "0.38.0" esp-idf-svc = "0.42.1" esp-idf-sys = { version = "0.31.6", features = ["binstart"] } log = "0.4.17" -retry = "1.3.1" -ssd1306 = "0.7.0" +sntpc = "0.3.1" + [build-dependencies] -anyhow = "1.0.57" -embuild = "0.29.1" +embuild = "0.29" +anyhow = "1" + +[patch.crates-io] +esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal", branch = "master" } diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..7a821d3 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,116 @@ +# esp32_c3_rust_wifi_demo + +## Dev Containers +This repository offers Dev Containers supports for: +- [Gitpod](https://gitpod.io/) + - ["Open in Gitpod" button](https://www.gitpod.io/docs/getting-started#open-in-gitpod-button) +- [VS Code Dev Containers](https://code.visualstudio.com/docs/remote/containers#_quick-start-open-an-existing-folder-in-a-container) +- [GitHub Codespaces](https://docs.github.com/en/codespaces/developing-in-codespaces/creating-a-codespace) +> **Note** +> +> In order to use Gitpod the project needs to be published in a GitLab, GitHub, +> or Bitbucket repository. +> +> In [order to use GitHub Codespaces](https://github.com/features/codespaces#faq) +> the project needs to be published in a GitHub repository and the user needs +> to be part of the Codespaces beta or have the project under an organization. + +If using VS Code or GitHub Codespaces, you can pull the image instead of building it +from the Dockerfile by selecting the `image` property instead of `build` in +`.devcontainer/devcontainer.json`. Further customization of the Dev Container can +be achived, see [.devcontainer.json reference](https://code.visualstudio.com/docs/remote/devcontainerjson-reference). + +When using Dev Containers, some tooling to facilitate building, flashing and +simulating in Wokwi is also added. +### Build +- Terminal approach: + + ``` + scripts/build.sh [debug | release] + ``` + > If no argument is passed, `release` will be used as default + + +- UI approach: + + The default build task is already set to build the project, and it can be used + in VS Code and Gitpod: + - From the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) (`Ctrl-Shift-P` or `Cmd-Shift-P`) run the `Tasks: Run Build Task` command. + - `Terminal`-> `Run Build Task` in the menu. + - With `Ctrl-Shift-B` or `Cmd-Shift-B`. + - From the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) (`Ctrl-Shift-P` or `Cmd-Shift-P`) run the `Tasks: Run Task` command and + select `Build`. + - From UI: Press `Build` on the left side of the Status Bar. + +### Flash + +> **Note** +> +> When using GitHub Codespaces, we need to make the ports +> public, [see instructions](https://docs.github.com/en/codespaces/developing-in-codespaces/forwarding-ports-in-your-codespace#sharing-a-port). + +- Terminal approach: + - Using `flash.sh` script: + + ``` + scripts/flash.sh [debug | release] + ``` + > If no argument is passed, `release` will be used as default + +- UI approach: + - From the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) (`Ctrl-Shift-P` or `Cmd-Shift-P`) run the `Tasks: Run Task` command and + select `Build & Flash`. + - From UI: Press `Build & Flash` on the left side of the Status Bar. +- Any alternative flashing method from host machine. + + +### Wokwi Simulation +When using a custom Wokwi project, please change the `WOKWI_PROJECT_ID` in +`run-wokwi.sh`. If no project id is specified, a DevKit for esp32c3 will be +used. +> **Warning** +> +> ESP32-S3 is not available in Wokwi + +- Terminal approach: + + ``` + scripts/run-wokwi.sh [debug | release] + ``` + > If no argument is passed, `release` will be used as default + +- UI approach: + + The default test task is already set to build the project, and it can be used + in VS Code and Gitpod: + - From the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) (`Ctrl-Shift-P` or `Cmd-Shift-P`) run the `Tasks: Run Test Task` command + - With `Ctrl-Shift-,` or `Cmd-Shift-,` + > **Note** + > + > This Shortcut is not available in Gitpod by default. + - From the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) (`Ctrl-Shift-P` or `Cmd-Shift-P`) run the `Tasks: Run Task` command and + select `Build & Run Wokwi`. + - From UI: Press `Build & Run Wokwi` on the left side of the Status Bar. + +> **Warning** +> +> The simulation will pause if the browser tab is in the background.This may +> affect the execution, specially when debuging. + +#### Debuging with Wokwi + +Wokwi offers debugging with GDB. + +- Terminal approach: + ``` + $HOME/.espressif/tools/riscv32-esp-elf/esp-2021r2-patch3-8.4.0/riscv32-esp-elf/bin/riscv32-esp-elf-gdb target/riscv32imc-esp-espidf/debug/esp32_c3_rust_wifi_demo -ex "target remote localhost:9333" + ``` + + > [Wokwi Blog: List of common GDB commands for debugging.](https://blog.wokwi.com/gdb-avr-arduino-cheatsheet/?utm_source=urish&utm_medium=blog) +- UI approach: + 1. Run the Wokwi Simulation in `debug` profile + 2. Go to `Run and Debug` section of the IDE (`Ctrl-Shift-D or Cmd-Shift-D`) + 3. Start Debugging by pressing the Play Button or pressing `F5` + 4. Choose the proper user: + - `esp` when using VS Code or GitHub Codespaces + - `gitpod` when using Gitpod diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..a22e3c6 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Gitpod and VsCode Codespaces tasks do not source the user environment +if [ "${USER}" == "gitpod" ]; then + which idf.py >/dev/null || { + source ~/export-esp.sh > /dev/null 2>&1 + } +elif [ "${CODESPACE_NAME}" != "" ]; then + which idf.py >/dev/null || { + source ~/export-esp.sh > /dev/null 2>&1 + } +fi + +case "$1" in + ""|"release") + cargo build --release + ;; + "debug") + cargo build + ;; + *) + echo "Wrong argument. Only \"debug\"/\"release\" arguments are supported" + exit 1;; +esac \ No newline at end of file diff --git a/scripts/flash.sh b/scripts/flash.sh new file mode 100755 index 0000000..915e1df --- /dev/null +++ b/scripts/flash.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -e + +BUILD_MODE="" +case "$1" in + ""|"release") + bash scripts/build.sh + BUILD_MODE="release" + ;; + "debug") + bash scripts/build.sh debug + BUILD_MODE="debug" + ;; + *) + echo "Wrong argument. Only \"debug\"/\"release\" arguments are supported" + exit 1;; +esac + +export ESP_ARCH=riscv32imc-esp-espidf + +web-flash --chip esp32c3 target/${ESP_ARCH}/${BUILD_MODE}/esp32-c3-rust-wifi-demo diff --git a/scripts/run-wokwi.sh b/scripts/run-wokwi.sh new file mode 100755 index 0000000..798b86a --- /dev/null +++ b/scripts/run-wokwi.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +set -e + +BUILD_MODE="" +case "$1" in + ""|"release") + bash scripts/build.sh + BUILD_MODE="release" + ;; + "debug") + bash scripts/build.sh debug + BUILD_MODE="debug" + ;; + *) + echo "Wrong argument. Only \"debug\"/\"release\" arguments are supported" + exit 1;; +esac + +if [ "${USER}" == "gitpod" ];then + gp_url=$(gp url 9012) + echo "gp_url=${gp_url}" + export WOKWI_HOST=${gp_url:8} +elif [ "${CODESPACE_NAME}" != "" ];then + export WOKWI_HOST=${CODESPACE_NAME}-9012.githubpreview.dev +fi + +export ESP_ARCH=riscv32imc-esp-espidf + +# TODO: Update with your Wokwi Project +export WOKWI_PROJECT_ID="" +if [ "${WOKWI_PROJECT_ID}" == "" ]; then + wokwi-server --chip esp32c3 target/${ESP_ARCH}/${BUILD_MODE}/esp32-c3-rust-wifi-demo +else + wokwi-server --chip esp32c3 --id ${WOKWI_PROJECT_ID} target/${ESP_ARCH}/${BUILD_MODE}/esp32-c3-rust-wifi-demo +fi \ No newline at end of file diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 0ac9af4..b12af40 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -6,5 +6,5 @@ CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 #CONFIG_FREERTOS_HZ=1000 # Workaround for https://github.com/espressif/esp-idf/issues/7631 -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=needs diff --git a/src/beep.rs b/src/beep.rs deleted file mode 100644 index 3a47022..0000000 --- a/src/beep.rs +++ /dev/null @@ -1,78 +0,0 @@ -use esp_idf_hal::gpio::OutputPin; -use esp_idf_hal::ledc; -use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; -use esp_idf_hal::prelude::*; -use esp_idf_sys::EspError; -use std::thread; -use std::time::Duration; - -type LedcChannel = Channel, P>; - -pub struct Beep { - beat: u8, - ringtone: ringtone::Type, - channel: LedcChannel, - duty: u32, -} - -impl Beep { - pub fn new(pin: P, timer: T, channel: C) -> Result { - let channel = Self::init_channel(pin, timer, channel)?; - - let max_duty = channel.get_max_duty(); - return Ok(Beep { - channel, - beat: 0, - duty: max_duty * 3 / 4, - ringtone: ringtone::SILENCE, - }); - } - - fn init_channel(pin: P, timer: T, channel: C) -> Result, EspError> { - let config = TimerConfig::default().frequency(2.kHz().into()); - let timer = Timer::new(timer, &config)?; - let channel: Channel, P> = Channel::new(channel, timer, pin)?; - return Ok(channel); - } - - pub fn play(&mut self, rx: &mut std::sync::mpsc::Receiver) { - loop { - let curr_ringtone = rx.try_recv().unwrap_or_else(|_| self.ringtone); - if !curr_ringtone.eq(&mut self.ringtone) { - self.beat = 0; - self.ringtone = curr_ringtone; - } - - let curr = curr_ringtone[self.beat as usize]; - if curr { - self.channel.set_duty(self.duty).expect("Failed to set duty"); - } else { - self.channel.set_duty(0).expect("Failed to set duty"); - } - thread::sleep(Duration::from_millis(100)); - - self.beat += 1; - if self.beat == 16 { - self.beat = 0; - } - } - } -} -pub mod ringtone { - - pub type Type = [bool; 16]; - - pub const ADAPTER_DOWN: Type = [ - true, true, true, true, false, false, false, false, false, false, false, false, false, - false, false, false, - ]; - pub const BATTERY_LOW: Type = [ - true, true, false, false, true, true, false, false, true, true, false, false, true, true, - false, false, - ]; - pub const SILENCE: Type = [false; 16]; - pub const SHUTDOWN: Type = [ - true, false, true, true, true, true, false, true, true, true, true, true, false, false, - false, false, - ]; -} diff --git a/src/blink.rs b/src/blink.rs deleted file mode 100644 index cc51890..0000000 --- a/src/blink.rs +++ /dev/null @@ -1,47 +0,0 @@ -use embedded_hal::digital::blocking::OutputPin; -use std::thread; -use std::time::Duration; - -pub struct Blink -where - T: OutputPin, -{ - state: bool, - pin: T, - stopped: bool, -} - -impl Blink -where - T: OutputPin, -{ - pub fn new(pin: T) -> Blink { - return Blink { - state: false, - pin, - stopped: false, - }; - } - - pub fn toggle(&mut self) -> Result<(), T::Error> { - self.state = !self.state; - if self.state { - self.pin.set_high() - } else { - self.pin.set_low() - } - } - - pub fn play(&mut self) { - loop { - if self.stopped { - break; - } - self.toggle().unwrap(); - thread::sleep(Duration::from_millis(50)); - - self.toggle().unwrap(); - thread::sleep(Duration::from_millis(950)); - } - } -} diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs deleted file mode 100644 index a7b940a..0000000 --- a/src/dc_out_controller.rs +++ /dev/null @@ -1,181 +0,0 @@ -use anyhow::{Ok, Result, anyhow}; -use retry; -use std::{ - sync::{ - mpsc, - Arc, Mutex, - }, - thread, - time::{self, SystemTime}, -}; - -use embedded_hal::digital::blocking::OutputPin; -use log::*; - -pub enum DcOutStatus { - On, - Off, - TurningOn(mpsc::Receiver), - TurningOff(mpsc::Receiver), -} - -impl DcOutStatus { - pub fn is_on(&self) -> bool { - match self { - DcOutStatus::On => true, - _ => false, - } - } - pub fn is_off(&self) -> bool { - match self { - DcOutStatus::Off => true, - _ => false, - } - } -} - -pub struct DcOutController

-where - P: OutputPin + Send, -{ - pin: Arc>, - shutdown_tx: Option>, - status: DcOutStatus, -} - -impl

DcOutController

-where - P: OutputPin + Send, - P: 'static, - P: std::marker::Sync, -{ - pub fn new(pin: P) -> Self { - return Self { - status: DcOutStatus::On, - pin: Arc::new(Mutex::new(pin)), - shutdown_tx: None, - }; - } - - pub fn open(&mut self) -> Result<()> { - if self.status.is_on() { - trace!("DC OUT already on, skipping"); - return Ok(()); - } - - let pin = self.pin.clone(); - let mut pin = retry::retry(retry::delay::Fixed::from_millis(100).take(10), || { - pin.lock() - }) - .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; - - retry::retry(retry::delay::Fixed::from_millis(100).take(10), || { - pin.set_low() - }) - .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; - - self.status = DcOutStatus::On; - info!("DC OUT ON"); - Ok(()) - } - - pub fn shutdown(&mut self) { - if self.shutdown_tx.is_some() { - trace!("Shutdown task already running, skipping..."); - return; - } - - info!("Shutdown task started..."); - let (tx, rx) = mpsc::channel(); - self.shutdown_tx = Some(tx); - - let pin = Arc::clone(&self.pin); - - let (status_tx, status_rx) = mpsc::channel(); - self.status = DcOutStatus::TurningOff(status_rx); - - thread::spawn(move || { - // Wait for computer shutdown finished - let target_at = SystemTime::now() + time::Duration::from_secs(10); - loop { - if rx.try_recv().is_ok_and(|state| !*state) { - break; - } - - if target_at > SystemTime::now() { - thread::sleep(time::Duration::from_millis(1000)); - continue; - } - - info!("Shutdown timeout, force shutdown..."); - - let result = || { - retry::retry(retry::delay::Fixed::from_millis(1000).take(10), || { - return pin - .lock() - .map_err(|_| anyhow::anyhow!("Can not lock pin"))? - .set_high() - .map_err(|_| anyhow::anyhow!("Can not shutdown")); - }).map_err(|_| anyhow!("Failed to send shutdown status"))?; - status_tx.send(true).map_err(|_| anyhow!("Failed to send shutdown status"))?; - Ok(()) - }; - - if let Err(e) = result() { - warn!("Failed to shutdown: {}", e); - } else { - info!("Shutdown finished"); - } - break; - } - }); - } - - pub fn stop_shutdown(&mut self) -> Result<()> { - if let Some(tx) = self.shutdown_tx.as_mut() { - info!("Shutdown task stopped..."); - if retry::retry(retry::delay::Fixed::from_millis(100).take(5), || { - tx.send(false) - }) - .map_err(|_| anyhow::anyhow!("Failed to stop shutdown")) - .is_err() { - warn!("Message to shutdown task was not sent"); - } else { - info!("Shutdown task stopped"); - } - self.shutdown_tx = None; - }; - Ok(()) - } - - pub fn get_status(&mut self) -> DcOutStatus { - match &self.status { - DcOutStatus::TurningOn(rx) => { - rx.try_recv().map_or((), |state| { - trace!("DcOutStatus::TurningOn({})", state); - if state { - self.status = DcOutStatus::On; - } else { - self.status = DcOutStatus::Off; - } - }) - }, - DcOutStatus::TurningOff(rx) => { - rx.try_recv().map_or((), |state| { - trace!("DcOutStatus::TurningOff({})", state); - if state { - self.status = DcOutStatus::Off; - } else { - self.status = DcOutStatus::On; - } - }) - }, - _default => {} - } - match &self.status { - DcOutStatus::On => DcOutStatus::On, - DcOutStatus::Off => DcOutStatus::Off, - _ => DcOutStatus::On, - } - } -} diff --git a/src/main.rs b/src/main.rs index ea4d027..7afc4a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,89 +1,48 @@ -#![feature(is_some_with)] +use std::{env, thread::sleep, time::{Duration, UNIX_EPOCH}}; + +use anyhow::{bail, Ok, Result}; +use embedded_svc::mqtt::client::{Publish, QoS}; use esp_idf_sys as _; -use log::{error, info}; -use std::{thread, time::Duration, sync::mpsc, env}; +use log::*; -use crate::wifi::WiFi; - -mod beep; -mod blink; -mod dc_out_controller; -mod manager; -mod screen; +mod message_queue; mod wifi; +mod time; + +use crate::{message_queue::MessageQueue, wifi::Internet, time::Time}; fn main() { env::set_var("DEFMT_LOG", "trace"); + env::set_var("RUST_BACKTRACE", "1"); env::set_var("RUST_LOG", "trace"); env_logger::init(); + // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once, // or else some patches to the runtime implemented by esp-idf-sys might not link properly. esp_idf_sys::link_patches(); + println!("Hello, world!"); - let peripherals = esp_idf_hal::peripherals::Peripherals::take().unwrap(); + let wifi = Internet::new().unwrap(); - let blink_pin = peripherals.pins.gpio5; - let beep_pin = peripherals.pins.gpio6; - let ledc_timer0 = peripherals.ledc.timer0; - let ledc_channel0 = peripherals.ledc.channel0; - let dc_out_ctl_pin = peripherals.pins.gpio3; - let i2c0 = peripherals.i2c0; - let sda_pin = peripherals.pins.gpio4; - let scl_pin = peripherals.pins.gpio10; + let mut mq = MessageQueue::new(); + mq.init().unwrap(); - let adc1 = peripherals.adc1; - let adapter_pin = peripherals.pins.gpio1; - let battery_pin = peripherals.pins.gpio2; - - let (tx, mut rx) = mpsc::channel(); - - info!("Starting"); - - thread::spawn(move || { - let mut blink = - blink::Blink::new(blink_pin.into_output().expect("Failed to set GPIO5 as output")); - blink.play(); - }); - - thread::spawn(move || { - thread::sleep(Duration::from_millis(5000)); - }); - - thread::spawn(move || { - let mut beep = beep::Beep::new( - beep_pin.into_output().expect("Failed to set GPIO6 as output"), - ledc_timer0, - ledc_channel0, - ) - .expect("Failed to create beep"); - beep.play(&mut rx); - }); - - let display = screen::Screen::new(i2c0, sda_pin, scl_pin).expect("Failed to create screen"); - - let dc_out_ctl = dc_out_controller::DcOutController::new( - dc_out_ctl_pin - .into_output() - .expect("Failed to set GPIO3 as output"), - ); - let mut manager = manager::Manager::new( - dc_out_ctl, - display, - adc1, - adapter_pin.into_analog_atten_11db().expect("Failed to set GPIO1 as analog input"), - battery_pin.into_analog_atten_11db().expect("Failed to set GPIO2 as analog input"), - tx, - ).expect("Failed to create manager"); - - let wifi = WiFi::new().expect("Failed to connect wifi"); + let mut time = Time::new(); + time.sync().unwrap(); loop { - match manager.handling_once() { - Ok(_) => {} - Err(err) => { - error!("Exec manager tick task failed: {}", err); - } + sleep(Duration::from_millis(1000)); + if let Some(ref mut mq_client) = mq.client { + let timestamps = time.get_time().as_millis(); + info!("timestamps {}", timestamps); + mq_client.publish( + "ups-0.2/heartbeat", + QoS::AtMostOnce, + false, + timestamps.to_string().as_bytes(), + ).map_err(|err| { + warn!("publish heartbeat failed {}", err) + }).unwrap(); } - thread::sleep(Duration::from_millis(100)); } } diff --git a/src/manager.rs b/src/manager.rs deleted file mode 100644 index 3561701..0000000 --- a/src/manager.rs +++ /dev/null @@ -1,147 +0,0 @@ -use anyhow::{anyhow, Result}; -use embedded_hal::digital::blocking::OutputPin; -use embedded_hal_0_2::adc::OneShot; -use esp_idf_hal::{ - adc::{Atten11dB, PoweredAdc, ADC1}, - gpio::{Gpio1, Gpio2}, -}; -use log::*; -use std::{ - sync::mpsc, - thread, - time::{Duration, SystemTime}, -}; - -use crate::{beep::ringtone, dc_out_controller::DcOutController, screen::Screen}; - -type AdapterGpio = Gpio1>; -type BatteryGpio = Gpio2>; - -pub struct Manager -where - C: OutputPin + Send, -{ - dc_out_controller: DcOutController, - screen: Screen, - adc: PoweredAdc, - adapter_pin: AdapterGpio, - battery_pin: BatteryGpio, - tx: mpsc::Sender, - adapter_downed_at: Option, -} - -impl Manager -where - C: OutputPin + Send, - C: 'static, - C: std::marker::Sync, -{ - pub fn new( - dc_out_controller: DcOutController, - screen: Screen, - adc1: ADC1, - adapter_pin: AdapterGpio, - battery_pin: BatteryGpio, - tx: mpsc::Sender, - ) -> Result { - let adc = PoweredAdc::new( - adc1, - esp_idf_hal::adc::config::Config::new().calibration(true), - ) - .map_err(|err| anyhow!("Can not init Adc: {}", err))?; - return Ok(Manager { - dc_out_controller, - screen, - adc, - adapter_pin, - battery_pin, - tx, - adapter_downed_at: None, - }); - } - - pub fn get_adapter_voltage(&mut self) -> Result { - return Ok(self - .adc - .read(&mut self.adapter_pin) - .map_err(|err| anyhow!("Can not read adapter voltage. {:?}", err))? - as f32); - } - pub fn get_battery_voltage(&mut self) -> Result { - return Ok(self - .adc - .read(&mut self.battery_pin) - .map_err(|err| anyhow!("Can not read battery voltage. {:?}", err))? - as f32); - } - - pub fn handling_once(&mut self) -> Result<()> { - let mut adapter = 0.0_f32; - let mut battery = 0.0_f32; - for _ in 0..10 { - adapter += self.get_adapter_voltage()?; - battery += self.get_battery_voltage()?; - thread::sleep(Duration::from_millis(10)); - } - - adapter /= 10.0_f32; - battery /= 10.0_f32; - - if is_adapter_down(adapter) { - if self.dc_out_controller.get_status().is_off() { - self.tx - .send(ringtone::SILENCE) - .map_err(|err| anyhow!("Can not send silence to Beep. {:?}", err))?; - } else if is_battery_down(battery) { - self.tx - .send(ringtone::BATTERY_LOW) - .expect("Can not send message"); - } else if is_battery_low(battery) { - self.dc_out_controller.shutdown(); - if self.adapter_downed_at.is_none() { - info!("Recording adapter downed at: {:?}", SystemTime::now()); - self.adapter_downed_at = Some(SystemTime::now()); - } else if self - .adapter_downed_at - .is_some_and(|at| at.elapsed().is_ok_and(|dur| *dur > Duration::from_secs(5))) - { - self.tx - .send(ringtone::SHUTDOWN) - .map_err(|err| anyhow!("Can not send shutdown to Beep. {:?}", err))?; - } - } else { - self.tx - .send(ringtone::ADAPTER_DOWN) - .map_err(|err| anyhow!("Can not send adapter down to Beep. {:?}", err))?; - self.dc_out_controller - .stop_shutdown() - .map_err(|err| anyhow!("Can not stop shutdown. {:?}", err))?; - self.adapter_downed_at = None; - } - } else { - self.dc_out_controller - .stop_shutdown() - .expect("Can not stop shutdown"); - self.tx.send(ringtone::SILENCE).map_err(|err| anyhow!("Can not send silence to Beep. {:?}", err))?; - self.adapter_downed_at = None; - self.dc_out_controller.open()?; - } - self.screen - .draw_voltage(adapter, battery) - .map_err(|err| anyhow!("Can not draw voltage. {:?}", err))?; - - Ok(()) - } -} - -fn is_battery_low(battery: f32) -> bool { - battery < 1500.0 -} - -fn is_battery_down(battery: f32) -> bool { - battery < 500.0 -} - -fn is_adapter_down(adapter: f32) -> bool { - adapter < 1000.0 -} diff --git a/src/message_queue.rs b/src/message_queue.rs new file mode 100644 index 0000000..a899827 --- /dev/null +++ b/src/message_queue.rs @@ -0,0 +1,73 @@ +use std::thread; + +use anyhow::Result; +use embedded_svc::mqtt::client::{utils::ConnState, Client, Connection, MessageImpl, Publish, QoS}; +use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration}; +use esp_idf_sys::EspError; +use log::*; + +pub struct MessageQueue { + pub client: Option>>, +} + +impl MessageQueue { + pub fn new() -> MessageQueue { + return MessageQueue { client: None }; + } + + pub fn init(&mut self) -> Result<()> { + let conf = MqttClientConfiguration { + client_id: Some("rust-esp32-std-demo"), + crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach), + + ..Default::default() + }; + + let (mut client, mut connection) = + EspMqttClient::new_with_conn("mqtt://192.168.31.8:1883", &conf)?; + + info!("MQTT client started"); + thread::spawn(move || { + info!("MQTT Listening for messages"); + + while let Some(msg) = connection.next() { + match msg { + Err(e) => info!("MQTT Message ERROR: {}", e), + Ok(msg) => info!("MQTT Message: {:?}", msg), + } + } + + info!("MQTT connection loop exit"); + }); + + client + .subscribe("esp32-c3-rust-wifi-demo", QoS::AtMostOnce) + .map_err(|err| anyhow::anyhow!("subscribe message from queue failed. {}", err))?; + + info!("Subscribed to all topics (esp32-c3-rust-wifi-demo)"); + + client + .publish( + "esp32-c3-rust-wifi-demo/ping", + QoS::AtMostOnce, + false, + "Hello".as_bytes(), + ) + .map_err(|err| anyhow::anyhow!("publish message to queue failed. {}", err))?; + + info!("Published a hello message to topic \"esp32-c3-rust-wifi-demo/ping\""); + + self.client = Some(client); + anyhow::Ok(()) + } + + pub fn publish(&mut self, bytes: &[u8]) -> Result { + self.client + .as_mut() + .unwrap() + .publish("", QoS::AtMostOnce, false, bytes) + .map_err(|err| { + anyhow::anyhow!("publish message to queue was failed!. {}", err) + }) + } +} diff --git a/src/screen.rs b/src/screen.rs deleted file mode 100644 index 99f8f14..0000000 --- a/src/screen.rs +++ /dev/null @@ -1,142 +0,0 @@ -use anyhow::{anyhow, Result, Ok}; -use embedded_graphics::{ - mono_font::{ascii::FONT_10X20, iso_8859_10::FONT_6X10, MonoTextStyle}, - pixelcolor::Rgb565, - prelude::{Dimensions, Drawable, Point, Primitive, RgbColor}, - primitives::{PrimitiveStyleBuilder, Rectangle}, - text::Text, -}; -use embedded_hal::{delay::blocking::DelayUs, i2c::blocking::{I2c, Operation}}; -use esp_idf_hal::{ - delay, - gpio::{self}, - i2c::{self, Master, I2C0}, - prelude::*, -}; -use log::warn; -use ssd1306::{ - mode::{BufferedGraphicsMode, DisplayConfig}, - prelude::I2CInterface, - size::DisplaySize128x64, - Ssd1306, -}; - -type Display = Ssd1306< - I2CInterface, gpio::Gpio10>>, - DisplaySize128x64, - BufferedGraphicsMode, ->; - -pub struct Screen { - pub display: Option, -} - -impl Screen { - pub fn new( - i2c: i2c::I2C0, - sda: gpio::Gpio4, - scl: gpio::Gpio10, - ) -> Result { - let config = ::default().baudrate(400.kHz().into()); - let mut i2c = i2c::Master::::new(i2c, i2c::MasterPins { sda, scl }, config)?; - - let mut buff = [0u8; 10]; - if let Err(err) = i2c.transaction(0x3C, &mut [Operation::Read(&mut buff)]) { - warn!("Failed to initialize display: {}", err); - warn!("Failed to initialize display: {}", err); - warn!("Failed to initialize display: {}", err); - warn!("Failed to initialize display: {}", err); - warn!("Failed to initialize display: {}", err); - return Ok(Self { display: None }); - } - - let di = ssd1306::I2CDisplayInterface::new(i2c); - - - let mut delay = delay::Ets; - delay.delay_ms(10_u32)?; - - let mut display = ssd1306::Ssd1306::new( - di, - ssd1306::size::DisplaySize128x64, - ssd1306::rotation::DisplayRotation::Rotate0, - ) - .into_buffered_graphics_mode(); - - display - .init() - .map_err(|err| anyhow!("Can not init display: {:?}", err))?; - - let mut instance = Screen { display: Some(display) }; - - instance.draw_boot()?; - - Ok(instance) - } - - pub fn draw_boot(&mut self) -> Result<()> { - if self.display.is_none() { - return Ok(()); - } - let display = self.display.as_mut().unwrap(); - - display.clear(); - - Rectangle::new( - display.bounding_box().top_left, - display.bounding_box().size, - ) - .into_styled( - PrimitiveStyleBuilder::new() - .fill_color(Rgb565::BLUE.into()) - .stroke_color(Rgb565::YELLOW.into()) - .stroke_width(1) - .build(), - ) - .draw(display) - .expect("Failed to draw rectangle"); - - Text::new( - "Ivan's UPS", - Point::new( - 12, - (display.bounding_box().size.height - 10) as i32 / 2 + 1, - ), - MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE.into()), - ) - .draw(display) - .expect("Failed to draw text"); - - display - .flush() - .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; - - Ok(()) - } - - pub fn draw_voltage(&mut self, adapter: f32, battery: f32) -> Result<()> { - if let Some(display) = self.display.as_mut() { - display.clear(); - - Text::new( - format!("Adp. {:.2} mV", adapter).as_str(), - Point::new(12, 24), - MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE.into()), - ) - .draw(display) - .expect("Failed to draw text"); - Text::new( - format!("Bat. {:.2} mV", battery).as_str(), - Point::new(12, 36), - MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE.into()), - ) - .draw(display) - .expect("Failed to draw text"); - display - .flush() - .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; - } - - Ok(()) - } -} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..8ffa0c4 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,105 @@ +use anyhow::bail; +use embedded_svc::sys_time::SystemTime; +use esp_idf_svc::sntp::{self, EspSntp}; +use esp_idf_svc::systime::EspSystemTime; +use log::{warn, info}; +use sntpc::{Error, NtpContext, NtpResult, NtpTimestampGenerator, NtpUdpSocket, Result}; +use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; +use std::time::{Duration}; + +pub struct Time { + result: Option, + sntp: Option>, +} + +impl Time { + pub fn new () -> Time { + return Time { + result: None, + sntp: None + } + } + + pub fn sync(&mut self) -> anyhow::Result<()> { + let sntp = sntp::EspSntp::new_default().map_err(|err| { + anyhow::anyhow!("ESP SNTP Failed: {:?}", err) + })?; + self.sntp = Some(Box::new(sntp)); + return anyhow::Ok(()) + } + + pub fn get_time(&mut self) -> Duration { + if let Some(ref mut sntp) = self.sntp { + info!("ESP SNTP sync status {:?}", sntp.get_sync_status()); + } + EspSystemTime {}.now() + } + + // pub fn sync(&mut self) -> anyhow::Result<()> { + // let mut error = None; + + // for _ in 0..10 { + // let socket = UdpSocket::bind("0.0.0.0:0").expect("Unable to crate UDP socket"); + // socket + // .set_read_timeout(Some(Duration::from_secs(2))) + // .expect("Unable to set UDP socket read timeout"); + // let sock_wrapper = UdpSocketWrapper(socket); + // let ntp_context = NtpContext::new(StdTimestampGen::default()); + + // let result = sntpc::get_time("pool.ntp.org:123", sock_wrapper, ntp_context); + // match result { + // Ok(res_time) => { + // println!("Got time: {}.{}", res_time.sec(), res_time.sec_fraction()); + // self.result = Some(res_time); + // return anyhow::Ok(()) + // } + // Err(err) => { + // error = Some(err); + // warn!("fetch dateTime from ntp failed: {:?}", err); + // }, + // } + // } + // bail!("fetch dateTime from ntp failed: {:?}", error) + // } + +} + +#[derive(Copy, Clone, Default)] +struct StdTimestampGen { + duration: Duration, +} + +impl NtpTimestampGenerator for StdTimestampGen { + fn init(&mut self) { + self.duration = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap(); + } + + fn timestamp_sec(&self) -> u64 { + self.duration.as_secs() + } + + fn timestamp_subsec_micros(&self) -> u32 { + self.duration.subsec_micros() + } +} + +#[derive(Debug)] +struct UdpSocketWrapper(UdpSocket); + +impl NtpUdpSocket for UdpSocketWrapper { + fn send_to(&self, buf: &[u8], addr: T) -> Result { + match self.0.send_to(buf, addr) { + Ok(usize) => Ok(usize), + Err(_) => Err(Error::Network), + } + } + + fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr)> { + match self.0.recv_from(buf) { + Ok((size, addr)) => Ok((size, addr)), + Err(_) => Err(Error::Network), + } + } +} diff --git a/src/wifi.rs b/src/wifi.rs index 3513d79..830a447 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -1,81 +1,77 @@ use std::{sync::Arc, time::Duration}; -use anyhow::{ - bail, - Result, Ok, -}; +use anyhow::{bail, Ok, Result}; +use embedded_svc::{ipv4, wifi::*}; use esp_idf_svc::ping::EspPing; use esp_idf_svc::{ - netif::EspNetifStack, nvs::EspDefaultNvs, sysloop::EspSysLoopStack, wifi::EspWifi + netif::EspNetifStack, nvs::EspDefaultNvs, sysloop::EspSysLoopStack, wifi::EspWifi, }; -use embedded_svc::{wifi::*, ipv4}; use log::info; use embedded_svc::ping::Ping; -pub struct WiFi { +pub struct Internet { wifi: Box, + auto_connect: bool, + connected: bool, } -impl WiFi { +impl Internet { pub fn new() -> Result { let netif_stack = Arc::new(EspNetifStack::new()?); let sys_loop_stack = Arc::new(EspSysLoopStack::new()?); let default_nvs = Arc::new(EspDefaultNvs::new()?); - let wifi = Self::wifi( - netif_stack.clone(), - sys_loop_stack.clone(), - default_nvs.clone(), - )?; + let wifi = Box::new(EspWifi::new(netif_stack, sys_loop_stack, default_nvs)?); - Ok(Self { wifi }) + let mut instance = Self { wifi, auto_connect: true, connected: false }; + instance.connect_ap()?; + + Ok(instance) } - fn wifi( - netif_stack: Arc, - sys_loop_stack: Arc, - default_nvs: Arc, - ) -> Result> { + fn connect_ap( + &mut self, + ) -> Result<()> { const SSID: &str = "Ivan Li"; const PASSWORD: &str = "ivanli.cc"; - let mut wifi = Box::new(EspWifi::new(netif_stack, sys_loop_stack, default_nvs)?); info!("Wifi created, about to scan"); - let ap_infos = wifi.scan()?; + let wifi = self.wifi.as_mut(); + // let ap_infos = wifi.scan()?; - info!("Wifi AP Count {}", ap_infos.len()); + // info!("Wifi AP Count {}", ap_infos.len()); - let ours = ap_infos.into_iter().find(|a| a.ssid == SSID); + // let ours = ap_infos.into_iter().find(|a| a.ssid == SSID); - let channel = if let Some(ours) = ours { - info!( - "Found configured access point {} on channel {}", - SSID, ours.channel - ); - Some(ours.channel) - } else { - info!( - "Configured access point {} not found during scanning, will go with unknown channel", - SSID - ); - None - }; + // let channel = if let Some(ours) = ours { + // info!( + // "Found configured access point {} on channel {}", + // SSID, ours.channel + // ); + // Some(ours.channel) + // } else { + // info!( + // "Configured access point {} not found during scanning, will go with unknown channel", + // SSID + // ); + // None + // }; - wifi.set_configuration(&Configuration::Mixed( + wifi.set_configuration(&Configuration::Client( ClientConfiguration { ssid: SSID.into(), password: PASSWORD.into(), - channel, - ..Default::default() - }, - AccessPointConfiguration { - ssid: "aptest".into(), - channel: channel.unwrap_or(1), + channel: None, ..Default::default() }, + // AccessPointConfiguration { + // ssid: "aptest".into(), + // channel: channel.unwrap_or(1), + // ..Default::default() + // }, ))?; info!("Wifi configuration set, about to get status"); @@ -85,11 +81,13 @@ impl WiFi { let status = wifi.get_status(); + info!("we have the wifi status"); + if let Status( ClientStatus::Started(ClientConnectionStatus::Connected(ClientIpStatus::Done( ip_settings, ))), - ApStatus::Started(ApIpStatus::Done), + ApStatus::Stopped ) = status { info!("Wifi connected"); @@ -99,22 +97,22 @@ impl WiFi { bail!("Unexpected Wifi status: {:?}", status); } - Ok(wifi) + Ok(()) } - fn ping(ip_settings: &ipv4::ClientSettings) -> Result<()> { - info!("About to do some pings for {:?}", ip_settings); - let ping_summary = - EspPing::default().ping(ip_settings.subnet.gateway, &Default::default())?; - if ping_summary.transmitted != ping_summary.received { - bail!( - "Pinging gateway {} resulted in timeouts", - ip_settings.subnet.gateway - ); + pub fn ping(ip_settings: &ipv4::ClientSettings) -> Result<()> { + info!("About to do some pings for {:?}", ip_settings); + let ping_summary = + EspPing::default().ping(ip_settings.subnet.gateway, &Default::default())?; + if ping_summary.transmitted != ping_summary.received { + bail!( + "Pinging gateway {} resulted in timeouts", + ip_settings.subnet.gateway + ); + } + + info!("Pinging done"); + + Ok(()) } - - info!("Pinging done"); - - Ok(()) -} -} +} \ No newline at end of file