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..b02a7ed 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,20 @@ 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-hal = "0.2.7" 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" +serde = {version="1.0.144", features = ["derive"]} +serde_json = "1.0.83" + [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/README.md b/README.md new file mode 100644 index 0000000..71603e9 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# UPS ESP32-C3 Rust + +一个使用 Rust 语言开发的 UPS 程序,适用于 乐鑫×安信可的 ESP32-C3-32S 模块。 + +## Features + +- [x] 输入电压、输出电压、电池电压检测; +- [x] Wi-Fi 联网; +- [x] 以 NTP 方式校准时间; +- [x] 以 MQTT 方式上报状态; +- [x] 提供 UPS 电源输出的控制信号; +- [ ] 提供 UPS 内置电池充电电路电源输入的控制信号; + +## GPIO 定义 + +- `GPIO 1`:UPS 输入电压检测,使用 `ADC 1`; +- `GPIO 2`:电池电芯电压检测,使用 `ADC 1`; +- `GPIO 3`:UPS 输出电压检测,使用 `ADC 1`; +- `GPIO 4`:蜂鸣器模拟信号输出,使用 `CHANNEL 0`, `TIMER 0`; +- `GPIO 5`:工作状态指示灯信号输出; +- `GPIO 6`:UPS 输出控制信号,适用于 P-MOS 开关; \ No newline at end of file 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 index 3a47022..7ab55b8 100644 --- a/src/beep.rs +++ b/src/beep.rs @@ -1,78 +1,124 @@ -use esp_idf_hal::gpio::OutputPin; -use esp_idf_hal::ledc; +use embedded_svc::timer::{PeriodicTimer, TimerService}; +use esp_idf_hal::gpio::{Gpio4, Output}; use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; +use esp_idf_hal::ledc::{CHANNEL0, TIMER0}; use esp_idf_hal::prelude::*; -use esp_idf_sys::EspError; -use std::thread; +use esp_idf_svc::timer::{EspTimer, EspTimerService}; +use esp_idf_sys::{ledc_mode_t_LEDC_LOW_SPEED_MODE, ledc_set_freq, EspError}; +use log::{info, warn}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; - type LedcChannel = Channel, P>; -pub struct Beep { +#[derive(Clone, Copy, Debug)] +struct BeepState { 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, +impl BeepState { + pub fn new() -> Self { + Self { beat: 0, - duty: max_duty * 3 / 4, ringtone: ringtone::SILENCE, + } + } + pub fn from_ringtone(ringtone: ringtone::Type) -> Self { + Self { beat: 0, ringtone } + } +} + +type BeepChannel = Channel, Gpio4>; + +pub struct Beep { + watch_timer: Option, + state: BeepState, + channel: Arc>, +} +impl Beep { + pub fn new() -> anyhow::Result { + let pin = unsafe { Gpio4::::new() } + .into_output() + .map_err(|err| anyhow::anyhow!("Failed to set GPIO 4 as ledc output. {}", err))?; + let hw_timer = unsafe { TIMER0::new() }; + let hw_channel = unsafe { CHANNEL0::new() }; + let channel = Self::init_channel(pin, hw_timer, hw_channel, 1000.Hz().into()) + .map_err(|err| anyhow::anyhow!("Failed to initialize channel. {}", err))?; + + return anyhow::Ok(Beep { + watch_timer: None, + state: BeepState::new(), + channel: Arc::new(Mutex::new(channel)), }); } - - fn init_channel(pin: P, timer: T, channel: C) -> Result, EspError> { - let config = TimerConfig::default().frequency(2.kHz().into()); + fn init_channel( + pin: Gpio4, + timer: TIMER0, + channel: CHANNEL0, + frequency: Hertz, + ) -> Result, TIMER0, CHANNEL0>, EspError> { + let config = TimerConfig::default().frequency(frequency); let timer = Timer::new(timer, &config)?; - let channel: Channel, P> = Channel::new(channel, timer, pin)?; + let channel = 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 fn play(&mut self, ringtone: ringtone::Type) -> anyhow::Result<()> { + if self.state.ringtone != ringtone { + self.state = BeepState::from_ringtone(ringtone); + info!("change: {:?}", ringtone); + } else { + return Ok(()); } + let state = Arc::new(Mutex::new(self.state)); + let channel = self.channel.to_owned(); + let mut timer = EspTimerService::new()? + .timer(move || match state.lock().as_mut() { + Ok(state) => { + if let Err(err) = Self::play_once(state, channel.lock().unwrap()) { + warn!("{}", err); + } + } + Err(err) => { + warn!("Failed to lock state. {}", err); + } + }) + .map_err(|err| anyhow::anyhow!("Init Timer Failed. {}", err))?; + timer.every(Duration::from_millis(250))?; + + self.watch_timer = Some(timer); + return anyhow::Ok(()); + } + + fn play_once( + state: &mut BeepState, + mut channel: MutexGuard, + ) -> anyhow::Result<()> { + if state.ringtone.len() <= state.beat as usize { + state.beat = 0; + } + let curr = state.ringtone[state.beat as usize]; + if curr == 0 { + channel.set_duty(0).expect("Failed to set duty"); + } else { + unsafe { ledc_set_freq(ledc_mode_t_LEDC_LOW_SPEED_MODE, 0, curr); } + channel.set_duty(60).expect("Failed to set duty"); + } + + state.beat += 1; + if state.beat == 16 { + state.beat = 0; + } + return anyhow::Ok(()); } } 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 type Type = [u32; 16]; + pub const ADAPTER_DOWN: Type = [2300, 2000, 2250, 1950, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; pub const BATTERY_LOW: Type = [ - true, true, false, false, true, true, false, false, true, true, false, false, true, true, - false, false, + 2000, 1950, 0, 0, 1980, 1900, 0, 0, 2000, 1900, 0, 0, 1980, 1900, 0, 0, ]; - pub const SILENCE: Type = [false; 16]; + pub const SILENCE: Type = [0; 16]; pub const SHUTDOWN: Type = [ - true, false, true, true, true, true, false, true, true, true, true, true, false, false, - false, false, + 3450, 3500, 0, 3500, 3050, 3000, 0, 3000, 3050, 1000, 1000, 1000, 0, 0, 0, 0, ]; } diff --git a/src/blink.rs b/src/blink.rs index cc51890..638dafd 100644 --- a/src/blink.rs +++ b/src/blink.rs @@ -1,4 +1,4 @@ -use embedded_hal::digital::blocking::OutputPin; +use embedded_hal::digital::v2::OutputPin; use std::thread; use std::time::Duration; @@ -32,16 +32,16 @@ where } } - pub fn play(&mut self) { + pub fn play(&mut self) where ::Error: std::fmt::Debug { loop { if self.stopped { break; } self.toggle().unwrap(); - thread::sleep(Duration::from_millis(50)); + thread::sleep(Duration::from_millis(10)); self.toggle().unwrap(); - thread::sleep(Duration::from_millis(950)); + thread::sleep(Duration::from_millis(990)); } } } diff --git a/src/charge_controller.rs b/src/charge_controller.rs new file mode 100644 index 0000000..9b58ee8 --- /dev/null +++ b/src/charge_controller.rs @@ -0,0 +1,187 @@ +use std::{ + sync::{Arc, Mutex, MutexGuard}, + time::Duration, +}; + +use embedded_hal::digital::v2::{OutputPin, PinState}; +use embedded_svc::event_bus::{EventBus, Postbox}; +use esp_idf_hal::gpio::{Gpio7, Output}; +use esp_idf_svc::eventloop::{ + Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, + EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User, +}; +use esp_idf_sys::c_types; +use log::warn; +use serde_json::json; + +use crate::{ + time::Time, + voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, +}; + +pub static mut CHARGE_STATE_EVENT_LOOP: Option< + EspEventLoop>, +> = None; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ChargeStatus { + Charging, + Charged, +} + +#[derive(Debug, Clone, Copy)] +pub struct ChargeControllerState { + pub status: ChargeStatus, + pub pin_state: PinState, + pub charge_deadline_at: Duration, +} + +impl ChargeControllerState { + pub fn new() -> Self { + Self { + status: ChargeStatus::Charging, + pin_state: PinState::Low, + charge_deadline_at: Duration::ZERO, + } + } + + pub fn to_json(&self) -> String { + let status = match self.status { + ChargeStatus::Charging => "Charging", + ChargeStatus::Charged => "Charged", + }; + let pin_state = match self.pin_state { + PinState::Low => "Low", + PinState::High => "High", + }; + let now = Time::new().get_time(); + let charging_count_down = if now > self.charge_deadline_at { + -1i64 + } else { + (self.charge_deadline_at - now).as_secs() as i64 + }; + json!({ "status": status, "pin_state": pin_state, "charging_count_down": charging_count_down}).to_string() + } +} + +impl EspTypedEventSource for ChargeControllerState { + fn source() -> *const c_types::c_char { + b"Charge\0".as_ptr() as *const _ + } +} + +impl EspTypedEventSerializer for ChargeControllerState { + fn serialize( + event: &ChargeControllerState, + f: impl for<'a> FnOnce(&'a EspEventPostData) -> R, + ) -> R { + f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) }) + } +} + +impl EspTypedEventDeserializer for ChargeControllerState { + fn deserialize( + data: &EspEventFetchData, + f: &mut impl for<'a> FnMut(&'a ChargeControllerState) -> R, + ) -> R { + f(unsafe { data.as_payload() }) + } +} + +type ChargeCtlPin = Gpio7; + +pub struct ChargeController { + pub state: ChargeControllerState, + voltage_subscription: Option>>, + pin: Arc>, +} + +impl ChargeController { + pub fn new() -> anyhow::Result { + let pin = unsafe { Gpio7::::new() } + .into_output() + .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; + + match EspBackgroundEventLoop::new(&Default::default()) { + Ok(eventloop) => unsafe { CHARGE_STATE_EVENT_LOOP = Some(eventloop) }, + Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), + } + + anyhow::Ok(Self { + state: ChargeControllerState::new(), + voltage_subscription: None, + pin: Arc::new(Mutex::new(pin)), + }) + } + + pub fn watch(&mut self) -> anyhow::Result<()> { + let mut state = self.state.to_owned(); + let pin = self.pin.to_owned(); + + if let Some(event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + let voltage_subscription = event_loop + .subscribe(move |obj: &VoltageDetectionWorker| { + match state.status { + ChargeStatus::Charging => { + if obj.battery_voltage < 12600 { + state.status = ChargeStatus::Charging; + } else { + let now = Time::new().get_time(); + if state.charge_deadline_at == Duration::ZERO { + state.charge_deadline_at = now + Duration::from_secs(600); + } else if now > state.charge_deadline_at { + state.status = ChargeStatus::Charged; + } else { + state.status = ChargeStatus::Charging; + } + } + } + ChargeStatus::Charged => { + if obj.battery_voltage < 10500 { + state.status = ChargeStatus::Charging; + } else { + state.status = ChargeStatus::Charged; + } + } + } + + match pin.lock() { + Ok(pin) => { + if let Err(err) = Self::output_ctl(&mut state, pin) { + warn!("Put Control Pin State Failed. {}", err); + } + } + Err(_) => todo!(), + } + + if let Some(event_loop) = unsafe { CHARGE_STATE_EVENT_LOOP.as_mut() } { + if let Err(err) = event_loop.post(&state, None) { + warn!("Post DC Out Status Failed. {}", err); + } + } else { + warn!("CHARGE_STATE_EVENT_LOOP is None"); + } + }) + .map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?; + self.voltage_subscription = Some(voltage_subscription); + } else { + anyhow::bail!("Voltage Event Loop is None"); + } + anyhow::Ok(()) + } + fn output_ctl( + state: &mut ChargeControllerState, + mut pin: MutexGuard, + ) -> anyhow::Result<()> { + if ChargeStatus::Charging == state.status { + pin.set_high() + .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin High Failed. {}", err))?; + state.pin_state = PinState::High; + } else if ChargeStatus::Charged == state.status { + pin.set_low() + .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin Low Failed. {}", err))?; + state.pin_state = PinState::Low; + } + return anyhow::Ok(()); + } +} diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index a7b940a..86a0200 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -1,181 +1,251 @@ -use anyhow::{Ok, Result, anyhow}; -use retry; use std::{ - sync::{ - mpsc, - Arc, Mutex, - }, - thread, - time::{self, SystemTime}, + sync::{Arc, Mutex, MutexGuard}, + time::Duration, }; -use embedded_hal::digital::blocking::OutputPin; -use log::*; +use embedded_hal::digital::v2::{OutputPin, PinState}; +use embedded_svc::event_bus::{EventBus, Postbox}; +use esp_idf_hal::gpio::{Gpio6, Output}; +use esp_idf_svc::eventloop::{ + Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, + EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User, +}; +use esp_idf_sys::c_types; +use log::warn; +use serde_json::json; +use crate::{ + time::Time, + voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, +}; + +const WAITING_OFF_DURATION: u64 = 60; +const WAITING_ON_DURATION: u64 = 60; + +pub static mut DC_OUT_STATE_EVENT_LOOP: Option< + EspEventLoop>, +> = None; + +#[derive(Debug, Clone, Copy, PartialEq)] pub enum DcOutStatus { + WaitingOn(Duration), On, Off, - TurningOn(mpsc::Receiver), - TurningOff(mpsc::Receiver), + WaitingOff, + TurningOff(Duration), } -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, - } - } +#[derive(Debug, Clone, Copy)] +pub struct DcOutControllerState { + pub status: DcOutStatus, + pub pin_state: PinState, } -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 { +impl DcOutControllerState { + pub fn new() -> Self { + Self { status: DcOutStatus::On, - pin: Arc::new(Mutex::new(pin)), - shutdown_tx: None, + pin_state: PinState::Low, + } + } + + fn handle_adapter_down(&mut self) { + match self.status { + DcOutStatus::On => { + self.status = DcOutStatus::WaitingOff; + } + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(target) => { + let now = Time::new().get_time(); + if now > target { + self.status = DcOutStatus::Off; + } + } + _ => {} }; } - 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; + fn turn_off(&mut self) { + let now = Time::new().get_time(); + match self.status { + DcOutStatus::On => { + self.status = + DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION)); } - }); - } - - 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"); + DcOutStatus::WaitingOff => { + self.status = + DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION)); } - self.shutdown_tx = None; + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(target) => { + if target < now { + self.status = DcOutStatus::Off; + } + } + _ => {} }; - 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, + fn turn_on(&mut self) { + let now = Time::new().get_time(); + match self.status { + DcOutStatus::WaitingOff => { + self.status = DcOutStatus::On; + } + DcOutStatus::Off => { + self.status = + DcOutStatus::WaitingOn(now + Duration::from_secs(WAITING_ON_DURATION)); + } + DcOutStatus::WaitingOn(target) => { + if target <= now { + self.status = DcOutStatus::On; + } + } + DcOutStatus::TurningOff(target) => { + if target <= now { + self.status = DcOutStatus::On; + } + } + _ => {} + }; + } + + pub fn to_json(&self) -> String { + let status = match self.status { + DcOutStatus::WaitingOn(_) => "WaitingOn", + DcOutStatus::On => "On", + DcOutStatus::Off => "Off", + DcOutStatus::WaitingOff => "WaitingOff", + DcOutStatus::TurningOff(_) => "TurningOff", + }; + let pin_state = match self.pin_state { + PinState::Low => "Low", + PinState::High => "High", + }; + let now = Time::new().get_time(); + let target = match self.status { + DcOutStatus::WaitingOn(target) => target, + DcOutStatus::TurningOff(target) => target, + _ => Duration::ZERO, + }; + let seconds = if now < target { + target - now + } else { + Duration::ZERO } + .as_secs(); + json!({ "status": status, "pin_state": pin_state, "seconds": seconds }).to_string() + } +} + +impl EspTypedEventSource for DcOutControllerState { + fn source() -> *const c_types::c_char { + b"DcOut\0".as_ptr() as *const _ + } +} + +impl EspTypedEventSerializer for DcOutControllerState { + fn serialize( + event: &DcOutControllerState, + f: impl for<'a> FnOnce(&'a EspEventPostData) -> R, + ) -> R { + f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) }) + } +} + +impl EspTypedEventDeserializer for DcOutControllerState { + fn deserialize( + data: &EspEventFetchData, + f: &mut impl for<'a> FnMut(&'a DcOutControllerState) -> R, + ) -> R { + f(unsafe { data.as_payload() }) + } +} + +type DcOutPin = Gpio6; + +pub struct DcOutController { + pub state: DcOutControllerState, + voltage_subscription: Option>>, + pin: Arc>, +} + +impl DcOutController { + pub fn new() -> anyhow::Result { + let pin = unsafe { Gpio6::::new() } + .into_output() + .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; + + match EspBackgroundEventLoop::new(&Default::default()) { + Ok(eventloop) => unsafe { DC_OUT_STATE_EVENT_LOOP = Some(eventloop) }, + Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), + } + + anyhow::Ok(Self { + state: DcOutControllerState::new(), + voltage_subscription: None, + pin: Arc::new(Mutex::new(pin)), + }) + } + + pub fn watch(&mut self) -> anyhow::Result<()> { + let mut state = self.state.to_owned(); + let pin = self.pin.to_owned(); + + if let Some(event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + let voltage_subscription = event_loop + .subscribe(move |obj: &VoltageDetectionWorker| { + if obj.adapter_voltage < 1000 { + if obj.battery_voltage < 1000 { + state.turn_off(); + } else { + state.handle_adapter_down(); + } + } else { + state.turn_on(); + } + + match pin.lock() { + Ok(pin) => { + if let Err(err) = Self::output_ctl(&mut state, pin) { + warn!("Put Control Pin State Failed. {}", err); + } + } + Err(_) => todo!(), + } + + if let Some(event_loop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } { + if let Err(err) = event_loop.post(&state, None) { + warn!("Post DC Out Status Failed. {}", err); + } + } else { + warn!("DC_OUT_STATE_EVENT_LOOP is None"); + } + }) + .map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?; + self.voltage_subscription = Some(voltage_subscription); + } else { + anyhow::bail!("Voltage Event Loop is None"); + } + anyhow::Ok(()) + } + fn output_ctl( + state: &mut DcOutControllerState, + mut pin: MutexGuard, + ) -> anyhow::Result<()> { + if DcOutStatus::Off == state.status { + pin.set_high() + .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin High Failed. {}", err))?; + state.pin_state = PinState::High; + } else if DcOutStatus::On == state.status { + pin.set_low() + .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin Low Failed. {}", err))?; + state.pin_state = PinState::Low; + } + return anyhow::Ok(()); } } diff --git a/src/main.rs b/src/main.rs index ea4d027..76e4e02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,89 +1,188 @@ -#![feature(is_some_with)] -use esp_idf_sys as _; -use log::{error, info}; -use std::{thread, time::Duration, sync::mpsc, env}; - -use crate::wifi::WiFi; +use embedded_svc::event_bus::{EventBus}; +use esp_idf_svc::eventloop::{EspBackgroundEventLoop}; +use esp_idf_sys::{self as _}; +use log::*; +use std::{ + env, + thread::{self, sleep}, + time::Duration, +}; mod beep; mod blink; mod dc_out_controller; -mod manager; -mod screen; +mod message_queue; +mod time; +mod voltage_detection; mod wifi; +mod charge_controller; + +use crate::{ + beep::{ringtone, Beep}, + dc_out_controller::{DcOutController, DcOutControllerState, DC_OUT_STATE_EVENT_LOOP}, + message_queue::MqDto, + voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, charge_controller::{CHARGE_STATE_EVENT_LOOP, ChargeControllerState, ChargeController}, +}; +use crate::{ + message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet, +}; + 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(); + info!("Hello, world!"); let peripherals = esp_idf_hal::peripherals::Peripherals::take().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 adc1 = peripherals.adc1; - let adapter_pin = peripherals.pins.gpio1; - let battery_pin = peripherals.pins.gpio2; - - let (tx, mut rx) = mpsc::channel(); - - info!("Starting"); + match EspBackgroundEventLoop::new(&Default::default()) { + Ok(eventloop) => unsafe { VOLTAGE_EVENTLOOP = Some(eventloop) }, + Err(err) => error!("Init Event Loop failed. {:?}", err), + }; thread::spawn(move || { - let mut blink = - blink::Blink::new(blink_pin.into_output().expect("Failed to set GPIO5 as output")); + 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)); - }); + let voltage_detection = VoltageDetection::new(); - 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); - }); + voltage_detection.unwrap() + .watching() + .expect("Can not watch voltages."); - let display = screen::Screen::new(i2c0, sda_pin, scl_pin).expect("Failed to create screen"); + let _wifi = Internet::new().unwrap(); - 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 mut time = Time::new(); + time.sync().unwrap(); - let wifi = WiFi::new().expect("Failed to connect wifi"); + sleep(Duration::from_millis(100)); + + let mut beep = Beep::new().unwrap(); + + let mut dc_out_controller = + DcOutController::new().expect("Can not get DcOutController instance"); + dc_out_controller + .watch() + .expect("Can not watch for dc_out_controller"); + + let mut charge_controller = ChargeController::new().expect("Can not get ChargeController instance"); + charge_controller + .watch() + .expect("Can not watch for charge_controller"); + + + sleep(Duration::from_millis(100)); + + let mut _mq = MessageQueue::new(); + + let _mq_subscription; + match _mq.watch() { + Err(err) => { + error!("Can not watch MessageQueue. {}", err); + } + Ok(subscription) => _mq_subscription = subscription, + } + + let _mq_tx_for_voltage = _mq.tx.clone(); + let _mq_tx_for_dc_out_state = _mq.tx.clone(); + let _mq_tx_for_charge_state = _mq.tx.clone(); + + let _voltage_subscription; + if let Some(voltage_event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + _voltage_subscription = voltage_event_loop + .subscribe(move |message: &VoltageDetectionWorker| { + if let Ok(json_str) = serde_json::to_string(&message) { + match _mq_tx_for_voltage.lock() { + Ok(tx) => { + let result = tx.send(MqDto { + topic: "voltage".to_string(), + message: json_str, + }); + if let Err(err) = result { + warn!("send voltage to mq message failed. {}", err) + } + } + Err(err) => warn!("send voltage to mq message failed. {}", err), + } + } + }) + .expect(" Listening Event Loop Failed"); + } else { + panic!("VOLTAGE_EVENTLOOP is undefined!"); + } + + let _dc_out_state_subscription; + if let Some(dc_state_event_loop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } { + _dc_out_state_subscription = dc_state_event_loop + .subscribe(move |message: &DcOutControllerState| { + match message.status { + dc_out_controller::DcOutStatus::WaitingOff => { + beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep.") + } + dc_out_controller::DcOutStatus::WaitingOn(_) => { + beep.play(ringtone::BATTERY_LOW).expect("Can not beep.") + } + dc_out_controller::DcOutStatus::TurningOff(_) => { + beep.play(ringtone::SHUTDOWN).expect("Can not beep.") + } + _ => beep.play(ringtone::SILENCE).expect("Can not beep."), + } + match _mq_tx_for_dc_out_state.lock() { + Ok(tx) => { + let result = tx.send(MqDto { + topic: "dc_out_state".to_string(), + message: message.to_json(), + }); + + if let Err(err) = result { + warn!("send dc_out_state message failed. {}", err) + } + } + Err(err) => warn!("send dc_out_state to mq message failed. {}", err), + } + }) + .expect(" Listening Event Loop Failed"); + } else { + panic!("DC_OUT_STATE_EVENT_LOOP is undefined!"); + } + + let _charge_state_subscription; + if let Some(charge_state_event_loop) = unsafe { CHARGE_STATE_EVENT_LOOP.as_mut() } { + _charge_state_subscription = charge_state_event_loop + .subscribe(move |message: &ChargeControllerState| { + match _mq_tx_for_charge_state.lock() { + Ok(tx) => { + let result = tx.send(MqDto { + topic: "charge_state".to_string(), + message: message.to_json(), + }); + + if let Err(err) = result { + warn!("send charge_state message failed. {}", err) + } + } + Err(err) => warn!("send charge_state to mq message failed. {}", err), + } + }) + .expect(" Listening Event Loop Failed"); + } else { + panic!("CHARGE_STATE_EVENT_LOOP is undefined!"); + } loop { - match manager.handling_once() { - Ok(_) => {} - Err(err) => { - error!("Exec manager tick task failed: {}", err); - } - } - thread::sleep(Duration::from_millis(100)); + 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..6155db4 --- /dev/null +++ b/src/message_queue.rs @@ -0,0 +1,113 @@ +use std::{ + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, Mutex, + }, + thread::{self, spawn}, + time::Duration, +}; + +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 tx: Arc>>, + pub rx: Arc>>, +} + +pub struct MessageQueueWatcher { + pub client: EspMqttClient>, +} + +impl MessageQueueWatcher { + pub fn new() -> 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.11: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\""); + + anyhow::Ok(Self { client }) + } + + pub fn publish(&mut self, topic: &str, bytes: &[u8]) -> Result { + self.client + .publish( + format!("{}/{}", "ups_0_2", topic).as_str(), + QoS::AtMostOnce, + false, + bytes, + ) + .map_err(|err| anyhow::anyhow!("publish message to queue was failed!. {}", err)) + } +} + +impl MessageQueue { + pub fn new() -> Self { + let (tx, rx) = mpsc::channel(); + return MessageQueue { + tx: Arc::new(Mutex::new(tx)), + rx: Arc::new(Mutex::new(rx)), + }; + } + + pub fn watch(&mut self) -> anyhow::Result<()> { + let mut watcher = MessageQueueWatcher::new() + .map_err(|err| anyhow::anyhow!("Create MessageQueueWatcher failed. {}", err))?; + let rx = self.rx.to_owned(); + // let (tx, rx) = mpsc::channel::(); + spawn(move || loop { + if let Ok(dto) = rx.lock().unwrap().recv_timeout(Duration::from_millis(400)) { + if let Err(err) = watcher.publish(dto.topic.as_str(), dto.message.as_bytes()) { + warn!("Can not publish message to MQTT. {}", err); + } + } + thread::sleep(Duration::from_millis(100)) + }); + anyhow::Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct MqDto { + pub message: String, + pub topic: String, +} 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..baf1a36 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,33 @@ +use embedded_svc::sys_time::SystemTime; +use esp_idf_svc::sntp::{self, EspSntp}; +use esp_idf_svc::systime::EspSystemTime; +use log::{info}; +use std::time::{Duration}; + +pub struct Time { + sntp: Option>, +} + +impl Time { + pub fn new () -> Time { + return Time { + 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() + } + +} diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs new file mode 100644 index 0000000..f122cad --- /dev/null +++ b/src/voltage_detection.rs @@ -0,0 +1,219 @@ +use std::{ + sync::{Arc, Mutex}, + thread::{sleep, spawn}, + time::Duration, +}; + +use embedded_hal::{adc::Channel, prelude::_embedded_hal_adc_OneShot}; +use embedded_svc::event_bus::Postbox; +use esp_idf_hal::{ + adc::{ + config::{self}, + Analog, Atten6dB, PoweredAdc, ADC1, + }, + gpio::{Gpio1, Gpio2, Gpio3, Input}, +}; +use esp_idf_svc::eventloop::{ + Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, + EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, +}; +use esp_idf_sys::c_types; +use log::{debug, warn}; +use serde::{Deserialize, Serialize}; + +use crate::time::Time; + +pub static mut VOLTAGE_EVENTLOOP: Option>> = + None; + +const ADAPTER_OFFSET: f32 = 12002f32 / 900f32; +const BATTERY_OFFSET: f32 = 12002f32 / 900f32; +const OUTPUT_OFFSET: f32 = 12002f32 / 900f32; + +pub struct VoltageDetection { + pub worker: VoltageDetectionWorker, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +pub struct VoltageDetectionWorker { + pub adapter_voltage: u16, + pub battery_voltage: u16, + pub output_voltage: u16, +} + +impl VoltageDetection { + pub fn new() -> anyhow::Result { + return anyhow::Ok(Self { + worker: VoltageDetectionWorker::new(), + }); + } + + pub fn watching(&mut self) -> anyhow::Result<()> { + match EspBackgroundEventLoop::new(&Default::default()) { + Ok(eventloop) => unsafe { VOLTAGE_EVENTLOOP = Some(eventloop) }, + Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), + } + let worker = Arc::new(Mutex::new(self.worker)); + + spawn(move || { + let handler = || -> anyhow::Result<()> { + let mut adapter_pin = unsafe { Gpio1::::new() } + .into_analog_atten_6db() + .map_err(|err| { + anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err) + })?; + let mut battery_pin = unsafe { Gpio2::::new() } + .into_analog_atten_6db() + .map_err(|err| { + anyhow::anyhow!("Failed to set GPIO 2 as analog input. {}", err) + })?; + let mut output_pin = unsafe { Gpio3::::new() } + .into_analog_atten_6db() + .map_err(|err| { + anyhow::anyhow!("Failed to set GPIO 3 as analog input. {}", err) + })?; + + let mut adc = PoweredAdc::new(unsafe { ADC1::new() }, config::Config::new())?; + + let mut worker = worker.lock().map_err(|err| { + anyhow::anyhow!("Lock VoltageDetection Worker Failed. {}", err) + })?; + + let mut last_runing_at; + + loop { + last_runing_at = Time::new().get_time(); + match worker.read_once( + &mut adc, + &mut adapter_pin, + &mut battery_pin, + &mut output_pin, + ) { + Ok(_) => debug!( + "Adapter: {},\tBattery: {},\t Output: {}", + worker.adapter_voltage, worker.battery_voltage, worker.output_voltage + ), + Err(err) => warn!("Read Failed. {}", err), + } + + if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + if let Err(err) = eventloop.post( + &mut VoltageDetectionWorker { + adapter_voltage: worker.adapter_voltage, + battery_voltage: worker.battery_voltage, + output_voltage: worker.output_voltage, + }, + None, + ) { + warn!("Post Result to Event Loop failed. {}", err); + } + } else { + warn!("EVENTLOOP IS NONE"); + } + let mut delta = Time::new().get_time() - last_runing_at; + + if delta >= Duration::from_millis(5000) { + delta = Duration::ZERO + } + + sleep(Duration::from_millis(5000) - delta); + } + }; + + if let Err(err) = handler() { + warn!("init failed. {}", err) + } + }); + return anyhow::Ok(()); + } +} + +impl VoltageDetectionWorker { + pub fn new() -> Self { + return Self { + adapter_voltage: 0, + battery_voltage: 0, + output_voltage: 0, + }; + } + + pub fn read_once( + &mut self, + adc: &mut PoweredAdc, + adapter_pin: &mut Gpio1>, + battery_pin: &mut Gpio2>, + output_pin: &mut Gpio3>, + ) -> anyhow::Result<()> { + match self.read_pin_once(adc, adapter_pin) { + Ok(voltage) => { + self.adapter_voltage = ((voltage as f32) * ADAPTER_OFFSET) as u16; + } + Err(err) => { + warn!("Adapter Voltage read failed: {:?}", err); + } + } + + match self.read_pin_once(adc, battery_pin) { + Ok(voltage) => { + self.battery_voltage = ((voltage as f32) * BATTERY_OFFSET) as u16; + } + Err(err) => { + warn!("Adapter Voltage read failed: {:?}", err); + } + } + match self.read_pin_once(adc, output_pin) { + Ok(voltage) => { + self.output_voltage = ((voltage as f32) * OUTPUT_OFFSET) as u16; + } + Err(err) => { + warn!("Adapter Voltage read failed: {:?}", err); + } + } + + return anyhow::Ok(()); + } + pub fn read_pin_once, PIN: Channel>( + &mut self, + adc: &mut PoweredAdc, + pin: &mut PIN, + ) -> anyhow::Result { + let mut avg_voltage: u16 = 0; + for _ in 0..10 { + let voltage = adc.read(pin); + match voltage { + Ok(voltage) => { + avg_voltage += voltage; + } + Err(err) => { + anyhow::bail!("Adapter Voltage read failed: {:?}", err) + } + }; + sleep(Duration::from_millis(100)); + } + anyhow::Ok(avg_voltage / 10) + } +} + +impl EspTypedEventSource for VoltageDetectionWorker { + fn source() -> *const c_types::c_char { + b"VOLTAGES\0".as_ptr() as *const _ + } +} + +impl EspTypedEventSerializer for VoltageDetectionWorker { + fn serialize( + event: &VoltageDetectionWorker, + f: impl for<'a> FnOnce(&'a EspEventPostData) -> R, + ) -> R { + f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) }) + } +} + +impl EspTypedEventDeserializer for VoltageDetectionWorker { + fn deserialize( + data: &EspEventFetchData, + f: &mut impl for<'a> FnMut(&'a VoltageDetectionWorker) -> R, + ) -> R { + f(unsafe { data.as_payload() }) + } +} diff --git a/src/wifi.rs b/src/wifi.rs index 3513d79..cd90536 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> { - const SSID: &str = "Ivan Li"; + fn connect_ap( + &mut self, + ) -> Result<()> { + const SSID: &str = "Ivan"; 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