From 282e1a01a28991b0b6f3839c333c8a3fae6d5725 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 7 Aug 2022 11:57:05 +0800 Subject: [PATCH 01/20] =?UTF-8?q?feat:=20wifi=20=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=EF=BC=8C=E7=BD=91=E7=BB=9C=E6=A0=A1=E6=97=B6=EF=BC=8CMQTT?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cargo/config.toml | 5 +- .devcontainer/Dockerfile | 45 ++++++++ .devcontainer/devcontainer.json | 45 ++++++++ .dockerignore | 1 + .gitpod.Dockerfile | 34 ++++++ .gitpod.yml | 23 ++++ Cargo.toml | 21 ++-- docs/README.md | 116 ++++++++++++++++++++ scripts/build.sh | 24 +++++ scripts/flash.sh | 22 ++++ scripts/run-wokwi.sh | 36 +++++++ sdkconfig.defaults | 4 +- src/beep.rs | 78 -------------- src/blink.rs | 47 --------- src/dc_out_controller.rs | 181 -------------------------------- src/main.rs | 99 +++++------------ src/manager.rs | 147 -------------------------- src/message_queue.rs | 73 +++++++++++++ src/screen.rs | 142 ------------------------- src/time.rs | 105 ++++++++++++++++++ src/wifi.rs | 118 ++++++++++----------- 21 files changed, 625 insertions(+), 741 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .dockerignore create mode 100644 .gitpod.Dockerfile create mode 100644 .gitpod.yml create mode 100644 docs/README.md create mode 100755 scripts/build.sh create mode 100755 scripts/flash.sh create mode 100755 scripts/run-wokwi.sh delete mode 100644 src/beep.rs delete mode 100644 src/blink.rs delete mode 100644 src/dc_out_controller.rs delete mode 100644 src/manager.rs create mode 100644 src/message_queue.rs delete mode 100644 src/screen.rs create mode 100644 src/time.rs 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 From 50580050317a67d38d59b68d6b073ad589eec467 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 7 Aug 2022 16:15:59 +0800 Subject: [PATCH 02/20] chore: remove unused code and dependencies. --- Cargo.toml | 1 - src/main.rs | 7 +++--- src/time.rs | 71 ----------------------------------------------------- src/wifi.rs | 2 +- 4 files changed, 4 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 494b084..ac29427 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ env_logger = "0.9.0" esp-idf-svc = "0.42.1" esp-idf-sys = { version = "0.31.6", features = ["binstart"] } log = "0.4.17" -sntpc = "0.3.1" [build-dependencies] diff --git a/src/main.rs b/src/main.rs index 7afc4a1..2cc21d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,4 @@ -use std::{env, thread::sleep, time::{Duration, UNIX_EPOCH}}; - -use anyhow::{bail, Ok, Result}; +use std::{env, thread::sleep, time::{Duration}}; use embedded_svc::mqtt::client::{Publish, QoS}; use esp_idf_sys as _; use log::*; @@ -22,7 +20,7 @@ fn main() { println!("Hello, world!"); - let wifi = Internet::new().unwrap(); + let _wifi = Internet::new().unwrap(); let mut mq = MessageQueue::new(); mq.init().unwrap(); @@ -34,6 +32,7 @@ fn main() { 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", diff --git a/src/time.rs b/src/time.rs index 8ffa0c4..93a2d73 100644 --- a/src/time.rs +++ b/src/time.rs @@ -3,19 +3,15 @@ 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 } } @@ -35,71 +31,4 @@ impl Time { 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 830a447..cd90536 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -33,7 +33,7 @@ impl Internet { fn connect_ap( &mut self, ) -> Result<()> { - const SSID: &str = "Ivan Li"; + const SSID: &str = "Ivan"; const PASSWORD: &str = "ivanli.cc"; info!("Wifi created, about to scan"); From d20344fe5eb54df6052ff9f92241df0f46c20ee9 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 21 Aug 2022 14:12:55 +0800 Subject: [PATCH 03/20] feat: blink, voltage detection. --- Cargo.toml | 2 + src/blink.rs | 47 +++++++++++++++ src/main.rs | 58 ++++++++++++++---- src/message_queue.rs | 2 +- src/voltage_detection.rs | 124 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 src/blink.rs create mode 100644 src/voltage_detection.rs diff --git a/Cargo.toml b/Cargo.toml index ac29427..c5179f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,10 @@ pio = ["esp-idf-sys/pio"] [dependencies] anyhow = "1" +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" diff --git a/src/blink.rs b/src/blink.rs new file mode 100644 index 0000000..e54e65e --- /dev/null +++ b/src/blink.rs @@ -0,0 +1,47 @@ +use embedded_hal::digital::v2::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) where ::Error: std::fmt::Debug { + loop { + if self.stopped { + break; + } + self.toggle().unwrap(); + thread::sleep(Duration::from_millis(30)); + + self.toggle().unwrap(); + thread::sleep(Duration::from_millis(1930)); + } + } +} diff --git a/src/main.rs b/src/main.rs index 2cc21d0..126e62a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,22 @@ -use std::{env, thread::sleep, time::{Duration}}; use embedded_svc::mqtt::client::{Publish, QoS}; use esp_idf_sys as _; use log::*; +use std::{ + env, + thread::{self, sleep}, + time::Duration, +}; +mod blink; mod message_queue; -mod wifi; mod time; +mod wifi; +mod voltage_detection; + +use crate::{ + message_queue::MessageQueue, time::Time, wifi::Internet, voltage_detection::VoltageDetection, +}; -use crate::{message_queue::MessageQueue, wifi::Internet, time::Time}; fn main() { env::set_var("DEFMT_LOG", "trace"); env::set_var("RUST_BACKTRACE", "1"); @@ -18,7 +27,31 @@ fn main() { // or else some patches to the runtime implemented by esp-idf-sys might not link properly. esp_idf_sys::link_patches(); - println!("Hello, world!"); + 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; + + thread::spawn(move || { + let mut blink = blink::Blink::new( + blink_pin + .into_output() + .expect("Failed to set GPIO5 as output"), + ); + blink.play(); + }); + + let mut voltage_detection = VoltageDetection::new(); + + voltage_detection.watching().expect("Can not watch voltages."); let _wifi = Internet::new().unwrap(); @@ -34,14 +67,15 @@ fn main() { 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(); + mq_client + .publish( + "ups-0.2/heartbeat", + QoS::AtMostOnce, + false, + timestamps.to_string().as_bytes(), + ) + .map_err(|err| warn!("publish heartbeat failed {}", err)) + .unwrap(); } } } diff --git a/src/message_queue.rs b/src/message_queue.rs index a899827..59b4056 100644 --- a/src/message_queue.rs +++ b/src/message_queue.rs @@ -24,7 +24,7 @@ impl MessageQueue { }; let (mut client, mut connection) = - EspMqttClient::new_with_conn("mqtt://192.168.31.8:1883", &conf)?; + EspMqttClient::new_with_conn("mqtt://192.168.31.11:1883", &conf)?; info!("MQTT client started"); thread::spawn(move || { diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs new file mode 100644 index 0000000..77213d1 --- /dev/null +++ b/src/voltage_detection.rs @@ -0,0 +1,124 @@ +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + +use embedded_hal::{adc::Channel, prelude::_embedded_hal_adc_OneShot, digital::v2::OutputPin}; +use embedded_svc::timer::{PeriodicTimer, TimerService}; +use esp_idf_hal::{ + adc::{ + config::{self}, + Analog, PoweredAdc, ADC1, + }, + gpio::{Gpio1, Gpio2, Gpio3, Input, Pull}, +}; +use esp_idf_svc::timer::{EspTimer, EspTimerService}; +use log::{info, warn}; + +pub struct VoltageDetection { + pub worker: VoltageDetectionWorker, + watch_timer: Option, +} + +#[derive(Clone, Copy)] +pub struct VoltageDetectionWorker { + pub adapter_voltage: u16, + pub battery_voltage: u16, + pub output_voltage: u16, +} + +impl VoltageDetection { + pub fn new() -> Self { + return Self { + worker: VoltageDetectionWorker::new(), + watch_timer: None, + }; + } + + pub fn watching(&mut self) -> anyhow::Result<()> { + let worker = Arc::new(Mutex::new(self.worker)); + let mut timer = EspTimerService::new()?.timer(move || { + info!("One-shot timer triggered"); + match worker.as_ref().lock().as_mut() { + Ok(worker) => { + if let Err(err) = worker.read_once() { + warn!("Read Failed. {}", err); + } + info!( + "Adapter: {},\tBattery: {},\t Output: {}", + worker.adapter_voltage, worker.battery_voltage, worker.output_voltage + ); + } + Err(err) => { + warn!("Lock VoltageDetection Worker Failed. {}", err) + } + } + })?; + timer.every(Duration::from_secs(1))?; + + self.watch_timer = Some(timer); + 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) -> anyhow::Result<()> { + let adapter_pin = unsafe { Gpio1::::new() } + .into_analog_atten_11db() + .map_err(|err| anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err))?; + match self.read_pin_once(adapter_pin) { + Ok(voltage) => { + self.adapter_voltage = voltage; + } + Err(err) => { + warn!("Adapter Voltage read failed: {:?}", err); + } + } + + let battery_pin = unsafe { Gpio2::::new() } + .into_analog_atten_11db() + .map_err(|err| anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err))?; + match self.read_pin_once(battery_pin) { + Ok(voltage) => { + self.battery_voltage = voltage; + } + Err(err) => { + warn!("Adapter Voltage read failed: {:?}", err); + } + } + + let output_pin = unsafe { Gpio3::::new() } + .into_analog_atten_11db() + .map_err(|err| anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err))?; + match self.read_pin_once(output_pin) { + Ok(voltage) => { + self.output_voltage = voltage; + } + Err(err) => { + warn!("Adapter Voltage read failed: {:?}", err); + } + } + + return anyhow::Ok(()); + } + pub fn read_pin_once, PIN: Channel>( + &mut self, + mut pin: PIN, + ) -> anyhow::Result { + let mut adc = PoweredAdc::new(unsafe { ADC1::new() }, config::Config::new())?; + let voltage = adc.read(&mut pin); + match voltage { + Ok(voltage) => anyhow::Ok((self.adapter_voltage + voltage * 10) / 11), + Err(err) => { + anyhow::bail!("Adapter Voltage read failed: {:?}", err) + } + } + } +} From 3c8fdd124b8a89c80f2f6a5f5f0be510af454f24 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 21 Aug 2022 21:54:09 +0800 Subject: [PATCH 04/20] feat: reporting voltage via MQTT. --- Cargo.toml | 2 + src/blink.rs | 4 +- src/eventloop.rs | 0 src/main.rs | 62 ++++++++++++++++++++-------- src/message_queue.rs | 15 ++++--- src/voltage_detection.rs | 88 +++++++++++++++++++++++++++++++++------- 6 files changed, 130 insertions(+), 41 deletions(-) create mode 100644 src/eventloop.rs diff --git a/Cargo.toml b/Cargo.toml index c5179f9..b02a7ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,8 @@ 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" +serde = {version="1.0.144", features = ["derive"]} +serde_json = "1.0.83" [build-dependencies] diff --git a/src/blink.rs b/src/blink.rs index e54e65e..638dafd 100644 --- a/src/blink.rs +++ b/src/blink.rs @@ -38,10 +38,10 @@ where break; } self.toggle().unwrap(); - thread::sleep(Duration::from_millis(30)); + thread::sleep(Duration::from_millis(10)); self.toggle().unwrap(); - thread::sleep(Duration::from_millis(1930)); + thread::sleep(Duration::from_millis(990)); } } } diff --git a/src/eventloop.rs b/src/eventloop.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs index 126e62a..082a88d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ -use embedded_svc::mqtt::client::{Publish, QoS}; -use esp_idf_sys as _; +use embedded_svc::event_bus::EventBus; +use esp_idf_svc::eventloop::{Background, EspBackgroundEventLoop, EspEventLoop}; +use esp_idf_sys::{self as _, EspError}; use log::*; use std::{ env, @@ -10,13 +11,16 @@ use std::{ mod blink; mod message_queue; mod time; -mod wifi; mod voltage_detection; +mod wifi; +use crate::voltage_detection::VoltageDetectionWorker; use crate::{ - message_queue::MessageQueue, time::Time, wifi::Internet, voltage_detection::VoltageDetection, + message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet, }; +static mut EVENTLOOP: Option>> = None; + fn main() { env::set_var("DEFMT_LOG", "trace"); env::set_var("RUST_BACKTRACE", "1"); @@ -40,6 +44,12 @@ fn main() { let sda_pin = peripherals.pins.gpio4; let scl_pin = peripherals.pins.gpio10; + info!("About to start a background event loop"); + match EspBackgroundEventLoop::new(&Default::default()) { + Ok(eventloop) => unsafe { EVENTLOOP = Some(eventloop) }, + Err(err) => error!("Init Event Loop failed. {:?}", err), + }; + thread::spawn(move || { let mut blink = blink::Blink::new( blink_pin @@ -51,7 +61,9 @@ fn main() { let mut voltage_detection = VoltageDetection::new(); - voltage_detection.watching().expect("Can not watch voltages."); + voltage_detection + .watching() + .expect("Can not watch voltages."); let _wifi = Internet::new().unwrap(); @@ -61,21 +73,35 @@ fn main() { let mut time = Time::new(); time.sync().unwrap(); + let _subscription; + if let Some(eventloop) = unsafe { EVENTLOOP.as_mut() } { + _subscription = eventloop + .subscribe(move |message: &VoltageDetectionWorker| { + info!("Event Loop Value"); + if let Ok(json_str) = serde_json::to_string(&message) { + if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { + warn!("Can not publish message to MQTT. {}", err); + } + } + }) + .expect(" Listening Event Loop Failed"); + } + loop { sleep(Duration::from_millis(1000)); - if let Some(ref mut mq_client) = mq.client { - let timestamps = time.get_time().as_millis(); + // 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(); - } + // 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(); + // } } } diff --git a/src/message_queue.rs b/src/message_queue.rs index 59b4056..905eaac 100644 --- a/src/message_queue.rs +++ b/src/message_queue.rs @@ -61,13 +61,16 @@ impl MessageQueue { anyhow::Ok(()) } - pub fn publish(&mut self, bytes: &[u8]) -> Result { + pub fn publish(&mut self, topic: &str, bytes: &[u8]) -> Result { self.client - .as_mut() + .as_mut() .unwrap() - .publish("", QoS::AtMostOnce, false, bytes) - .map_err(|err| { - anyhow::anyhow!("publish message to queue was failed!. {}", err) - }) + .publish( + format!("{}/{}", "ups_0_2", topic).as_str(), + QoS::AtMostOnce, + false, + bytes, + ) + .map_err(|err| anyhow::anyhow!("publish message to queue was failed!. {}", err)) } } diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index 77213d1..c37fd78 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -3,24 +3,41 @@ use std::{ time::Duration, }; -use embedded_hal::{adc::Channel, prelude::_embedded_hal_adc_OneShot, digital::v2::OutputPin}; -use embedded_svc::timer::{PeriodicTimer, TimerService}; +use embedded_hal::{adc::Channel, prelude::_embedded_hal_adc_OneShot}; +use embedded_svc::{ + event_bus::Postbox, + timer::{PeriodicTimer, TimerService}, +}; use esp_idf_hal::{ adc::{ config::{self}, Analog, PoweredAdc, ADC1, }, - gpio::{Gpio1, Gpio2, Gpio3, Input, Pull}, + gpio::{Gpio1, Gpio2, Gpio3, Input}, }; -use esp_idf_svc::timer::{EspTimer, EspTimerService}; +use esp_idf_svc::{ + eventloop::{ + EspEventFetchData, EspEventPostData, EspTypedEventDeserializer, EspTypedEventSerializer, + EspTypedEventSource, + }, + timer::{EspTimer, EspTimerService}, +}; +use esp_idf_sys::c_types; use log::{info, warn}; +use serde::{Deserialize, Serialize}; + +use crate::EVENTLOOP; + +const ADAPTER_OFFSET: f32 = 12002f32 / 900f32; +const BATTERY_OFFSET: f32 = 12002f32 / 900f32; +const OUTPUT_OFFSET: f32 = 12002f32 / 900f32; pub struct VoltageDetection { pub worker: VoltageDetectionWorker, watch_timer: Option, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Serialize, Deserialize)] pub struct VoltageDetectionWorker { pub adapter_voltage: u16, pub battery_voltage: u16, @@ -39,7 +56,7 @@ impl VoltageDetection { let worker = Arc::new(Mutex::new(self.worker)); let mut timer = EspTimerService::new()?.timer(move || { info!("One-shot timer triggered"); - match worker.as_ref().lock().as_mut() { + match worker.lock().as_mut() { Ok(worker) => { if let Err(err) = worker.read_once() { warn!("Read Failed. {}", err); @@ -48,6 +65,22 @@ impl VoltageDetection { "Adapter: {},\tBattery: {},\t Output: {}", worker.adapter_voltage, worker.battery_voltage, worker.output_voltage ); + if let Some(eventloop) = unsafe { 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 { + info!("Event Loop Post"); + } + } else { + warn!("EVENTLOOP IS NONE"); + } } Err(err) => { warn!("Lock VoltageDetection Worker Failed. {}", err) @@ -60,6 +93,7 @@ impl VoltageDetection { return anyhow::Ok(()); } } + impl VoltageDetectionWorker { pub fn new() -> Self { return Self { @@ -71,11 +105,11 @@ impl VoltageDetectionWorker { pub fn read_once(&mut self) -> anyhow::Result<()> { let adapter_pin = unsafe { Gpio1::::new() } - .into_analog_atten_11db() + .into_analog_atten_6db() .map_err(|err| anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err))?; match self.read_pin_once(adapter_pin) { Ok(voltage) => { - self.adapter_voltage = voltage; + self.adapter_voltage = ((voltage as f32) * ADAPTER_OFFSET) as u16; } Err(err) => { warn!("Adapter Voltage read failed: {:?}", err); @@ -83,11 +117,11 @@ impl VoltageDetectionWorker { } let battery_pin = unsafe { Gpio2::::new() } - .into_analog_atten_11db() - .map_err(|err| anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err))?; + .into_analog_atten_6db() + .map_err(|err| anyhow::anyhow!("Failed to set GPIO 2 as analog input. {}", err))?; match self.read_pin_once(battery_pin) { Ok(voltage) => { - self.battery_voltage = voltage; + self.battery_voltage = ((voltage as f32) * BATTERY_OFFSET) as u16; } Err(err) => { warn!("Adapter Voltage read failed: {:?}", err); @@ -95,11 +129,11 @@ impl VoltageDetectionWorker { } let output_pin = unsafe { Gpio3::::new() } - .into_analog_atten_11db() - .map_err(|err| anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err))?; + .into_analog_atten_6db() + .map_err(|err| anyhow::anyhow!("Failed to set GPIO 3 as analog input. {}", err))?; match self.read_pin_once(output_pin) { Ok(voltage) => { - self.output_voltage = voltage; + self.output_voltage = ((voltage as f32) * OUTPUT_OFFSET) as u16; } Err(err) => { warn!("Adapter Voltage read failed: {:?}", err); @@ -115,10 +149,34 @@ impl VoltageDetectionWorker { let mut adc = PoweredAdc::new(unsafe { ADC1::new() }, config::Config::new())?; let voltage = adc.read(&mut pin); match voltage { - Ok(voltage) => anyhow::Ok((self.adapter_voltage + voltage * 10) / 11), + Ok(voltage) => anyhow::Ok(voltage), Err(err) => { anyhow::bail!("Adapter Voltage read failed: {:?}", err) } } } } + +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() }) + } +} From 3b2497cb7fd049475c98a6ceaa33a86ba9d5d097 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 28 Aug 2022 12:07:38 +0800 Subject: [PATCH 05/20] feat: beep module. --- src/beep.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 19 ++------- 2 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 src/beep.rs diff --git a/src/beep.rs b/src/beep.rs new file mode 100644 index 0000000..e295ccd --- /dev/null +++ b/src/beep.rs @@ -0,0 +1,108 @@ +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_svc::timer::{EspTimer, EspTimerService}; +use esp_idf_sys::EspError; +use log::{info, warn}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +type LedcChannel = Channel, P>; + +#[derive(Clone, Copy, Debug)] +struct BeepState { + beat: u8, + ringtone: ringtone::Type, +} + +impl BeepState { + pub fn new() -> Self { + Self { + beat: 0, + ringtone: ringtone::SILENCE, + } + } + pub fn from_ringtone(ringtone: ringtone::Type) -> Self { + Self { beat: 0, ringtone } + } +} + +pub struct Beep { + watch_timer: Option, + state: BeepState, +} +impl Beep { + pub fn new() -> Self { + return Beep { + watch_timer: None, + state: BeepState::new(), + }; + } + 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::new(channel, timer, pin)?; + return Ok(channel); + } + pub fn play(&mut self, ringtone: ringtone::Type) -> anyhow::Result<()> { + if self.state.ringtone != ringtone { + self.state = BeepState::from_ringtone(ringtone); + } + let state = Arc::new(Mutex::new(self.state)); + let mut timer = EspTimerService::new()?.timer(move || { + info!("One-shot timer triggered"); + match state.lock().as_mut() { + Ok(state) => { + info!("B state.beat: {}", state.beat); + if let Err(err) = Self::play_once(state) { + warn!("{}", err); + } + info!("A state.beat: {}", state.beat); + } + Err(err) => { + warn!("Failed to lock state. {}", err); + } + } + })?; + timer.every(Duration::from_millis(250))?; + + self.watch_timer = Some(timer); + return anyhow::Ok(()); + } + + fn play_once(state: &mut BeepState) -> 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 curr = state.ringtone[state.beat as usize]; + let mut channel = Self::init_channel(pin, hw_timer, hw_channel, if curr < 1000 { 1000.Hz().into()} else {curr.Hz().into()} ) + .map_err(|err| anyhow::anyhow!("Failed to initialize channel. {}", err))?; + + if curr < 1000 { + channel.set_duty(0).expect("Failed to set duty"); + } else { + 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 = [u32; 16]; + pub const ADAPTER_DOWN: Type = [2000, 1950, 1900, 1800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + pub const BATTERY_LOW: Type = [2000, 1950, 0, 0, 1930, 1900, 0, 0, 2000, 1900, 0, 0, 1930, 1900, 0, 0]; + pub const SILENCE: Type = [0; 16]; + pub const SHUTDOWN: Type = [2300, 0, 2300, 2000, 2100, 2000, 0, 2300, 2000, 2100, 2000, 1900, 0, 0, 0, 0]; +} diff --git a/src/main.rs b/src/main.rs index 082a88d..fddf1e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,13 +8,14 @@ use std::{ time::Duration, }; +mod beep; mod blink; mod message_queue; mod time; mod voltage_detection; mod wifi; -use crate::voltage_detection::VoltageDetectionWorker; +use crate::{beep::{Beep, ringtone}, voltage_detection::VoltageDetectionWorker}; use crate::{ message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet, }; @@ -58,6 +59,8 @@ fn main() { ); blink.play(); }); + let mut beep = Beep::new(); + beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep."); let mut voltage_detection = VoltageDetection::new(); @@ -89,19 +92,5 @@ fn main() { loop { 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(); - // } } } From 84eb469b8b4f80a7a1c31ce7e8a40a5f2fd3fa98 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 28 Aug 2022 15:09:46 +0800 Subject: [PATCH 06/20] feat: beep. --- src/beep.rs | 17 ++++++++++------- src/main.rs | 39 +++++++++++++++++++++++---------------- src/voltage_detection.rs | 1 - 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/beep.rs b/src/beep.rs index e295ccd..82f7b59 100644 --- a/src/beep.rs +++ b/src/beep.rs @@ -53,23 +53,23 @@ impl Beep { 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 mut timer = EspTimerService::new()?.timer(move || { - info!("One-shot timer triggered"); match state.lock().as_mut() { Ok(state) => { - info!("B state.beat: {}", state.beat); if let Err(err) = Self::play_once(state) { warn!("{}", err); } - info!("A state.beat: {}", state.beat); } 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); @@ -82,6 +82,9 @@ impl Beep { .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() }; + if state.ringtone.len() <= state.beat as usize { + state.beat = 0; + } let curr = state.ringtone[state.beat as usize]; let mut channel = Self::init_channel(pin, hw_timer, hw_channel, if curr < 1000 { 1000.Hz().into()} else {curr.Hz().into()} ) .map_err(|err| anyhow::anyhow!("Failed to initialize channel. {}", err))?; @@ -101,8 +104,8 @@ impl Beep { } pub mod ringtone { pub type Type = [u32; 16]; - pub const ADAPTER_DOWN: Type = [2000, 1950, 1900, 1800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - pub const BATTERY_LOW: Type = [2000, 1950, 0, 0, 1930, 1900, 0, 0, 2000, 1900, 0, 0, 1930, 1900, 0, 0]; + 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 = [2000, 1950, 0, 0, 1980, 1900, 0, 0, 2000, 1900, 0, 0, 1980, 1900, 0, 0]; pub const SILENCE: Type = [0; 16]; - pub const SHUTDOWN: Type = [2300, 0, 2300, 2000, 2100, 2000, 0, 2300, 2000, 2100, 2000, 1900, 0, 0, 0, 0]; + pub const SHUTDOWN: Type = [3450, 3500, 0, 3500, 3050, 3000, 0, 3000, 3050, 1000, 1000, 1000, 0, 0, 0, 0]; } diff --git a/src/main.rs b/src/main.rs index fddf1e1..047732d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,29 @@ fn main() { blink.play(); }); let mut beep = Beep::new(); - beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep."); + + let _subscription; + if let Some(eventloop) = unsafe { EVENTLOOP.as_mut() } { + _subscription = eventloop + .subscribe(move |message: &VoltageDetectionWorker| { + info!("Event Loop Value"); + if let Ok(json_str) = serde_json::to_string(&message) { + if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { + warn!("Can not publish message to MQTT. {}", err); + } + } + + if message.battery_voltage < 1000 { + beep.play(ringtone::BATTERY_LOW).expect("Can not beep."); + } else if message.adapter_voltage < 1000 { + beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep."); + } else { + beep.play(ringtone::SILENCE).expect("Can not beep."); + } + }) + .expect(" Listening Event Loop Failed"); + } + let mut voltage_detection = VoltageDetection::new(); @@ -75,21 +97,6 @@ fn main() { let mut time = Time::new(); time.sync().unwrap(); - - let _subscription; - if let Some(eventloop) = unsafe { EVENTLOOP.as_mut() } { - _subscription = eventloop - .subscribe(move |message: &VoltageDetectionWorker| { - info!("Event Loop Value"); - if let Ok(json_str) = serde_json::to_string(&message) { - if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { - warn!("Can not publish message to MQTT. {}", err); - } - } - }) - .expect(" Listening Event Loop Failed"); - } - loop { sleep(Duration::from_millis(1000)); } diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index c37fd78..959fdef 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -55,7 +55,6 @@ impl VoltageDetection { pub fn watching(&mut self) -> anyhow::Result<()> { let worker = Arc::new(Mutex::new(self.worker)); let mut timer = EspTimerService::new()?.timer(move || { - info!("One-shot timer triggered"); match worker.lock().as_mut() { Ok(worker) => { if let Err(err) = worker.read_once() { From 2a8e7db4b3f2123c9c10de78cd2e19150ab8902a Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 30 Aug 2022 22:36:22 +0800 Subject: [PATCH 07/20] feat: dc out controller. --- src/dc_out_controller.rs | 248 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 85 +++++++++----- src/voltage_detection.rs | 4 +- 3 files changed, 307 insertions(+), 30 deletions(-) create mode 100644 src/dc_out_controller.rs diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs new file mode 100644 index 0000000..ac7cb69 --- /dev/null +++ b/src/dc_out_controller.rs @@ -0,0 +1,248 @@ +use std::sync::{Arc, Mutex}; + +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::{info, warn}; +use serde_json::json; + +use crate::{voltage_detection::VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; + +const WAITING_OFF_SECONDS: u8 = 60; + +pub static mut DC_OUT_STATE_EVENT_LOOP: Option< + EspEventLoop>, +> = None; +static mut INSTANCE: Option = None; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DcOutStatus { + WaitingOn(u8), + On, + Off, + WaitingOff, + TurningOff(u8), +} + +#[derive(Debug, Clone, Copy)] +pub struct DcOutControllerState { + pub status: DcOutStatus, + pub pin_state: PinState, +} + +impl DcOutControllerState { + pub fn new() -> Self { + Self { + status: DcOutStatus::On, + pin_state: PinState::Low, + } + } + + fn handle_adapter_down(&mut self) { + info!("status: {:?}", self); + match self.status { + DcOutStatus::On => { + self.status = DcOutStatus::WaitingOff; + } + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + self.status = DcOutStatus::Off; + } + } + _ => {} + }; + } + + fn turn_off(&mut self) { + match self.status { + DcOutStatus::On => { + self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOff => { + self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + self.status = DcOutStatus::Off; + } + } + _ => {} + }; + } + + fn turn_on(&mut self) { + match self.status { + DcOutStatus::WaitingOff => { + self.status = DcOutStatus::On; + } + DcOutStatus::Off => { + self.status = DcOutStatus::WaitingOn(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOn(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::WaitingOn(seconds); + } else { + self.status = DcOutStatus::On; + } + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + 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 seconds: i16 = match self.status { + DcOutStatus::WaitingOn(seconds) => seconds.into(), + DcOutStatus::TurningOff(seconds) => seconds.into(), + _ => -1, + }; + json!({ "status": status, "pin_state": pin_state, "seconds": seconds }) + .to_string() + } +} + +impl EspTypedEventSource for DcOutControllerState { + fn source() -> *const c_types::c_char { + b"VOLTAGES\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() }) + } +} + +pub struct DcOutController { + pub state: DcOutControllerState, + voltage_subscription: Option>>, +} + +impl DcOutController { + fn new() -> Self { + Self { + state: DcOutControllerState::new(), + voltage_subscription: None, + } + } + + pub fn get_instance() -> anyhow::Result { + // if let Some(instance) = unsafe { &mut INSTANCE } { + // anyhow::Ok(*instance) + // } else { + let instance = Self::new(); + // unsafe { INSTANCE = Some(instance) }; + 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(instance) + // } + } + + pub fn watch(&mut self) -> anyhow::Result<()> { + let state = Arc::new(Mutex::new(self.state)); + + if let Some(event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + let voltage_subscription = event_loop + .subscribe( + move |obj: &VoltageDetectionWorker| match state.lock().as_mut() { + Ok(state) => { + if obj.adapter_voltage < 1000 { + if obj.battery_voltage < 1000 { + state.turn_off(); + } else { + state.handle_adapter_down(); + } + } else { + state.turn_on(); + } + let ref state = **state; + if let Err(err) = Self::output_ctl(*state) { + warn!("Put Control Pin State Failed. {}", err); + } + + 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"); + } + info!("status: {:?}", state); + } + Err(err) => warn!("Can not lock state. {}", err), + }, + ) + .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(mut state: DcOutControllerState) -> anyhow::Result<()> { + let mut pin = unsafe { Gpio6::::new() } + .into_output() + .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; + + if DcOutStatus::Off == state.status && state.pin_state == PinState::Low { + 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 && state.pin_state == PinState::High { + 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 047732d..b735c3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use embedded_svc::event_bus::EventBus; use esp_idf_svc::eventloop::{Background, EspBackgroundEventLoop, EspEventLoop}; -use esp_idf_sys::{self as _, EspError}; +use esp_idf_sys::{self as _}; use log::*; use std::{ env, @@ -10,17 +10,22 @@ use std::{ mod beep; mod blink; +mod dc_out_controller; mod message_queue; mod time; mod voltage_detection; mod wifi; -use crate::{beep::{Beep, ringtone}, voltage_detection::VoltageDetectionWorker}; +use crate::{ + beep::{ringtone, Beep}, + dc_out_controller::{DcOutController, DcOutControllerState, DC_OUT_STATE_EVENT_LOOP}, + voltage_detection::VoltageDetectionWorker, +}; use crate::{ message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet, }; -static mut EVENTLOOP: Option>> = None; +static mut VOLTAGE_EVENTLOOP: Option>> = None; fn main() { env::set_var("DEFMT_LOG", "trace"); @@ -47,7 +52,7 @@ fn main() { info!("About to start a background event loop"); match EspBackgroundEventLoop::new(&Default::default()) { - Ok(eventloop) => unsafe { EVENTLOOP = Some(eventloop) }, + Ok(eventloop) => unsafe { VOLTAGE_EVENTLOOP = Some(eventloop) }, Err(err) => error!("Init Event Loop failed. {:?}", err), }; @@ -59,30 +64,6 @@ fn main() { ); blink.play(); }); - let mut beep = Beep::new(); - - let _subscription; - if let Some(eventloop) = unsafe { EVENTLOOP.as_mut() } { - _subscription = eventloop - .subscribe(move |message: &VoltageDetectionWorker| { - info!("Event Loop Value"); - if let Ok(json_str) = serde_json::to_string(&message) { - if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { - warn!("Can not publish message to MQTT. {}", err); - } - } - - if message.battery_voltage < 1000 { - beep.play(ringtone::BATTERY_LOW).expect("Can not beep."); - } else if message.adapter_voltage < 1000 { - beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep."); - } else { - beep.play(ringtone::SILENCE).expect("Can not beep."); - } - }) - .expect(" Listening Event Loop Failed"); - } - let mut voltage_detection = VoltageDetection::new(); @@ -97,6 +78,54 @@ fn main() { let mut time = Time::new(); time.sync().unwrap(); + + let mut beep = Beep::new(); + + // let _voltage_subscription; + // if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + // _voltage_subscription = eventloop + // .subscribe(move |message: &VoltageDetectionWorker| { + // if let Ok(json_str) = serde_json::to_string(&message) { + // if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { + // warn!("Can not publish message to MQTT. {}", err); + // } + // } + // }) + // .expect(" Listening Event Loop Failed"); + // } + + let mut dc_out_controller = + DcOutController::get_instance().expect("Can not get DcOutController instance"); + dc_out_controller + .watch() + .expect("Can not watch for dc_out_controller"); + + let _dc_out_state_subscription; + if let Some(eventloop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } { + _dc_out_state_subscription = eventloop + .subscribe(move |message: &DcOutControllerState| { + info!("Event Loop Value"); + 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.")} + } + if let Err(err) = mq.publish("dcOut", message.to_json().as_bytes()) { + warn!("Can not publish message to MQTT. {}", err); + } + }) + .expect(" Listening Event Loop Failed"); + } else { + error!("DC_OUT_STATE_EVENT_LOOP is None"); + } + loop { sleep(Duration::from_millis(1000)); } diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index 959fdef..f952377 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -26,7 +26,7 @@ use esp_idf_sys::c_types; use log::{info, warn}; use serde::{Deserialize, Serialize}; -use crate::EVENTLOOP; +use crate::VOLTAGE_EVENTLOOP; const ADAPTER_OFFSET: f32 = 12002f32 / 900f32; const BATTERY_OFFSET: f32 = 12002f32 / 900f32; @@ -64,7 +64,7 @@ impl VoltageDetection { "Adapter: {},\tBattery: {},\t Output: {}", worker.adapter_voltage, worker.battery_voltage, worker.output_voltage ); - if let Some(eventloop) = unsafe { EVENTLOOP.as_mut() } { + if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { if let Err(err) = eventloop.post( &mut VoltageDetectionWorker { adapter_voltage: worker.adapter_voltage, From 25e2770d0194ff29f543a168d5008877439e587a Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sat, 10 Sep 2022 09:23:07 +0800 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20ESP=20?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=BE=AA=E7=8E=AF=E4=BC=A0=E9=80=92=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E3=80=82=EF=BC=88=E6=9C=89=E5=86=85=E5=AD=98=E8=B6=8A?= =?UTF-8?q?=E7=95=8C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dc_out_controller.rs | 2 +- src/main.rs | 133 ++++++++++++++++++++++++++------------- src/message_queue.rs | 77 +++++++++++++++++++++-- src/voltage_detection.rs | 8 ++- 4 files changed, 169 insertions(+), 51 deletions(-) diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index ac7cb69..43e0a84 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -11,7 +11,7 @@ use esp_idf_sys::c_types; use log::{info, warn}; use serde_json::json; -use crate::{voltage_detection::VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; +use crate::{voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}}; const WAITING_OFF_SECONDS: u8 = 60; diff --git a/src/main.rs b/src/main.rs index b735c3f..c3309b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ -use embedded_svc::event_bus::EventBus; +use embedded_svc::event_bus::{EventBus, Postbox}; use esp_idf_svc::eventloop::{Background, EspBackgroundEventLoop, EspEventLoop}; use esp_idf_sys::{self as _}; use log::*; use std::{ env, thread::{self, sleep}, - time::Duration, + time::Duration, borrow::Borrow, }; mod beep; @@ -19,14 +19,13 @@ mod wifi; use crate::{ beep::{ringtone, Beep}, dc_out_controller::{DcOutController, DcOutControllerState, DC_OUT_STATE_EVENT_LOOP}, - voltage_detection::VoltageDetectionWorker, + message_queue::{MqDto, MQ_EVENTLOOP}, + voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, }; use crate::{ message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet, }; -static mut VOLTAGE_EVENTLOOP: Option>> = None; - fn main() { env::set_var("DEFMT_LOG", "trace"); env::set_var("RUST_BACKTRACE", "1"); @@ -73,26 +72,12 @@ fn main() { let _wifi = Internet::new().unwrap(); - let mut mq = MessageQueue::new(); - mq.init().unwrap(); - let mut time = Time::new(); time.sync().unwrap(); - let mut beep = Beep::new(); + sleep(Duration::from_millis(100)); - // let _voltage_subscription; - // if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { - // _voltage_subscription = eventloop - // .subscribe(move |message: &VoltageDetectionWorker| { - // if let Ok(json_str) = serde_json::to_string(&message) { - // if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { - // warn!("Can not publish message to MQTT. {}", err); - // } - // } - // }) - // .expect(" Listening Event Loop Failed"); - // } + let mut beep = Beep::new(); let mut dc_out_controller = DcOutController::get_instance().expect("Can not get DcOutController instance"); @@ -100,33 +85,93 @@ fn main() { .watch() .expect("Can not watch for dc_out_controller"); - let _dc_out_state_subscription; - if let Some(eventloop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } { - _dc_out_state_subscription = eventloop - .subscribe(move |message: &DcOutControllerState| { - info!("Event Loop Value"); - match message.status { - dc_out_controller::DcOutStatus::WaitingOff => { - beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep.") + sleep(Duration::from_millis(100)); + + let mut mq = MessageQueue::new(); + mq.init().unwrap(); + + let _mq_subscription; + match mq.watch() { + Err(err) => { + error!("Can not watch MessageQueue. {}", err); + } + Ok(subscription) => _mq_subscription = subscription, + } + + let _voltage_subscription; + if let Some(voltage_event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + if let Some(mq_event_loop) = unsafe { MQ_EVENTLOOP.as_mut() } { + _voltage_subscription = voltage_event_loop + .subscribe(move |message: &VoltageDetectionWorker| { + if let Ok(json_str) = serde_json::to_string(&message) { + let result = mq_event_loop.post( + &MqDto { + topic: "voltage", + message: json_str.as_bytes(), + }, + None, + ); + match result { + Ok(success) => { + if !success { + warn!("post voltage to mq event failed.") + } + } + Err(err) => warn!("post voltage to mq event failed. {}", err), + } } - 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.")} - } - if let Err(err) = mq.publish("dcOut", message.to_json().as_bytes()) { - warn!("Can not publish message to MQTT. {}", err); - } - }) - .expect(" Listening Event Loop Failed"); + }) + .expect(" Listening Event Loop Failed"); + } else { + panic!("MQ_EVENTLOOP is undefined!"); + } } else { - error!("DC_OUT_STATE_EVENT_LOOP is None"); + 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() } { + if let Some(mq_event_loop) = unsafe { MQ_EVENTLOOP.as_mut() } { + _dc_out_state_subscription = dc_state_event_loop + .subscribe(move |message: &DcOutControllerState| { + info!("Event Loop Value"); + 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."), + } + let result = mq_event_loop.post( + &MqDto { + topic: "dc_out_state", + message: message.to_json().as_bytes(), + }, + None, + ); + match result { + Ok(success) => { + if !success { + warn!("post dc_out_state event failed.") + } + } + Err(err) => warn!("post dc_out_state event failed. {}", err), + } + }) + .expect(" Listening Event Loop Failed"); + } else { + panic!("MQ_EVENTLOOP is undefined!"); + } + } else { + panic!("DC_OUT_STATE_EVENT_LOOP is undefined!"); } loop { - sleep(Duration::from_millis(1000)); + sleep(Duration::from_millis(100)); } } diff --git a/src/message_queue.rs b/src/message_queue.rs index 905eaac..09d5790 100644 --- a/src/message_queue.rs +++ b/src/message_queue.rs @@ -1,18 +1,35 @@ 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 embedded_svc::{ + event_bus::EventBus, + mqtt::client::{utils::ConnState, Client, Connection, MessageImpl, Publish, QoS}, +}; +use esp_idf_svc::{ + eventloop::{ + Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, + EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, + User, + }, + mqtt::client::{EspMqttClient, MqttClientConfiguration}, +}; +use esp_idf_sys::{c_types, EspError}; use log::*; +use serde::{Deserialize, Serialize}; + +pub static mut MQ_EVENTLOOP: Option>> = None; pub struct MessageQueue { pub client: Option>>, + voltage_subscription: Option>>, } impl MessageQueue { pub fn new() -> MessageQueue { - return MessageQueue { client: None }; + return MessageQueue { + client: None, + voltage_subscription: None, + }; } pub fn init(&mut self) -> Result<()> { @@ -73,4 +90,56 @@ impl MessageQueue { ) .map_err(|err| anyhow::anyhow!("publish message to queue was failed!. {}", err)) } + + pub fn watch(mut self) -> anyhow::Result>> { + match EspBackgroundEventLoop::new(&Default::default()) { + Ok(eventloop) => unsafe { MQ_EVENTLOOP = Some(eventloop) }, + Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), + } + if let Some(eventloop) = unsafe { MQ_EVENTLOOP.as_mut() } { + let subscription = eventloop + .subscribe(move |dto: &MqDto| { + info!( + "publish data: {:?}", + unsafe { + String::from_utf8_unchecked(dto.message.to_vec()) + } + ); + if let Err(err) = self.publish(dto.topic, dto.message) { + warn!("Can not publish message to MQTT. {}", err); + } + }) + .map_err(|err| anyhow::anyhow!(" Listening Event Loop Failed. {}", err))?; + // self.voltage_subscription = Some(subscription); + return anyhow::Ok(subscription); + } + anyhow::bail!("MQ_EVENTLOOP is Not Defined.") + } +} + +#[derive(Copy, Debug, Clone, Serialize, Deserialize)] +pub struct MqDto<'a, 'b> { + pub topic: &'a str, + pub message: &'b [u8], +} + +impl EspTypedEventSource for MqDto<'_, '_> { + fn source() -> *const c_types::c_char { + b"MqDto\0".as_ptr() as *const _ + } +} + +impl EspTypedEventSerializer> for MqDto<'_, '_> { + fn serialize(event: &MqDto, f: impl for<'a> FnOnce(&'a EspEventPostData) -> R) -> R { + f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) }) + } +} + +impl<'b, 'c> EspTypedEventDeserializer> for MqDto<'b, 'c> { + fn deserialize( + data: &EspEventFetchData, + f: &mut impl for<'a> FnMut(&'a MqDto<'b, 'c>) -> R, + ) -> R { + f(unsafe { data.as_payload() }) + } } diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index f952377..3e2caad 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -18,7 +18,7 @@ use esp_idf_hal::{ use esp_idf_svc::{ eventloop::{ EspEventFetchData, EspEventPostData, EspTypedEventDeserializer, EspTypedEventSerializer, - EspTypedEventSource, + EspTypedEventSource, EspEventLoop, Background, EspBackgroundEventLoop, }, timer::{EspTimer, EspTimerService}, }; @@ -26,7 +26,7 @@ use esp_idf_sys::c_types; use log::{info, warn}; use serde::{Deserialize, Serialize}; -use crate::VOLTAGE_EVENTLOOP; +pub static mut VOLTAGE_EVENTLOOP: Option>> = None; const ADAPTER_OFFSET: f32 = 12002f32 / 900f32; const BATTERY_OFFSET: f32 = 12002f32 / 900f32; @@ -53,6 +53,10 @@ impl VoltageDetection { } 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)); let mut timer = EspTimerService::new()?.timer(move || { match worker.lock().as_mut() { From eb96ce4afb1a6fc93b0225d60b19853b8a3346a1 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sat, 10 Sep 2022 15:38:08 +0800 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E5=A4=84=E7=90=86=20MQTT=20=E5=8F=91=E9=80=81?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 120 +++++++++++++++++++----------------------- src/message_queue.rs | 122 ++++++++++++++++--------------------------- 2 files changed, 101 insertions(+), 141 deletions(-) diff --git a/src/main.rs b/src/main.rs index c3309b3..ad82b7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,11 @@ use esp_idf_svc::eventloop::{Background, EspBackgroundEventLoop, EspEventLoop}; use esp_idf_sys::{self as _}; use log::*; use std::{ + borrow::Borrow, env, + sync::{Arc, Mutex}, thread::{self, sleep}, - time::Duration, borrow::Borrow, + time::Duration, }; mod beep; @@ -19,7 +21,7 @@ mod wifi; use crate::{ beep::{ringtone, Beep}, dc_out_controller::{DcOutController, DcOutControllerState, DC_OUT_STATE_EVENT_LOOP}, - message_queue::{MqDto, MQ_EVENTLOOP}, + message_queue::MqDto, voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, }; use crate::{ @@ -41,13 +43,6 @@ fn main() { 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; info!("About to start a background event loop"); match EspBackgroundEventLoop::new(&Default::default()) { @@ -87,86 +82,81 @@ fn main() { sleep(Duration::from_millis(100)); - let mut mq = MessageQueue::new(); - mq.init().unwrap(); + let mut _mq = MessageQueue::new(); let _mq_subscription; - match mq.watch() { + 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 _voltage_subscription; if let Some(voltage_event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { - if let Some(mq_event_loop) = unsafe { MQ_EVENTLOOP.as_mut() } { - _voltage_subscription = voltage_event_loop - .subscribe(move |message: &VoltageDetectionWorker| { - if let Ok(json_str) = serde_json::to_string(&message) { - let result = mq_event_loop.post( - &MqDto { - topic: "voltage", - message: json_str.as_bytes(), - }, - None, - ); - match result { - Ok(success) => { - if !success { - warn!("post voltage to mq event failed.") + _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, + }); + match result { + Ok(()) => { + warn!("send voltage to mq message failed.") } + Err(err) => warn!("send voltage to mq message failed. {}", err), } - Err(err) => warn!("post voltage to mq event failed. {}", err), } + Err(err) => warn!("send voltage to mq message failed. {}", err), } - }) - .expect(" Listening Event Loop Failed"); - } else { - panic!("MQ_EVENTLOOP is undefined!"); - } + } + }) + .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() } { - if let Some(mq_event_loop) = unsafe { MQ_EVENTLOOP.as_mut() } { - _dc_out_state_subscription = dc_state_event_loop - .subscribe(move |message: &DcOutControllerState| { - info!("Event Loop Value"); - 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."), + _dc_out_state_subscription = dc_state_event_loop + .subscribe(move |message: &DcOutControllerState| { + info!("Event Loop Value"); + match message.status { + dc_out_controller::DcOutStatus::WaitingOff => { + beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep.") } - let result = mq_event_loop.post( - &MqDto { - topic: "dc_out_state", - message: message.to_json().as_bytes(), - }, - None, - ); - match result { - Ok(success) => { - if !success { - warn!("post dc_out_state event failed.") + 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(), + }); + + match result { + Ok(_) => { + info!("send dc_out_state message success.") } + Err(err) => warn!("send dc_out_state message failed. {}", err), } - Err(err) => warn!("post dc_out_state event failed. {}", err), } - }) - .expect(" Listening Event Loop Failed"); - } else { - panic!("MQ_EVENTLOOP is undefined!"); - } + 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!"); } diff --git a/src/message_queue.rs b/src/message_queue.rs index 09d5790..5c95911 100644 --- a/src/message_queue.rs +++ b/src/message_queue.rs @@ -1,38 +1,32 @@ -use std::thread; +use std::{ + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, Mutex, + }, + thread::{self, spawn}, + time::Duration, +}; use anyhow::Result; -use embedded_svc::{ - event_bus::EventBus, - mqtt::client::{utils::ConnState, Client, Connection, MessageImpl, Publish, QoS}, -}; +use embedded_svc::mqtt::client::{utils::ConnState, Client, Connection, MessageImpl, Publish, QoS}; use esp_idf_svc::{ - eventloop::{ - Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, - EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, - User, - }, + eventloop::{Background, EspEventLoop}, mqtt::client::{EspMqttClient, MqttClientConfiguration}, }; -use esp_idf_sys::{c_types, EspError}; +use esp_idf_sys::EspError; use log::*; -use serde::{Deserialize, Serialize}; - -pub static mut MQ_EVENTLOOP: Option>> = None; pub struct MessageQueue { - pub client: Option>>, - voltage_subscription: Option>>, + pub tx: Arc>>, + pub rx: Arc>>, } -impl MessageQueue { - pub fn new() -> MessageQueue { - return MessageQueue { - client: None, - voltage_subscription: None, - }; - } +pub struct MessageQueueWatcher { + pub client: EspMqttClient>, +} - pub fn init(&mut self) -> Result<()> { +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), @@ -74,14 +68,11 @@ impl MessageQueue { info!("Published a hello message to topic \"esp32-c3-rust-wifi-demo/ping\""); - self.client = Some(client); - anyhow::Ok(()) + anyhow::Ok(Self { client }) } pub fn publish(&mut self, topic: &str, bytes: &[u8]) -> Result { self.client - .as_mut() - .unwrap() .publish( format!("{}/{}", "ups_0_2", topic).as_str(), QoS::AtMostOnce, @@ -90,56 +81,35 @@ impl MessageQueue { ) .map_err(|err| anyhow::anyhow!("publish message to queue was failed!. {}", err)) } +} - pub fn watch(mut self) -> anyhow::Result>> { - match EspBackgroundEventLoop::new(&Default::default()) { - Ok(eventloop) => unsafe { MQ_EVENTLOOP = Some(eventloop) }, - Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), - } - if let Some(eventloop) = unsafe { MQ_EVENTLOOP.as_mut() } { - let subscription = eventloop - .subscribe(move |dto: &MqDto| { - info!( - "publish data: {:?}", - unsafe { - String::from_utf8_unchecked(dto.message.to_vec()) - } - ); - if let Err(err) = self.publish(dto.topic, dto.message) { - warn!("Can not publish message to MQTT. {}", err); - } - }) - .map_err(|err| anyhow::anyhow!(" Listening Event Loop Failed. {}", err))?; - // self.voltage_subscription = Some(subscription); - return anyhow::Ok(subscription); - } - anyhow::bail!("MQ_EVENTLOOP is Not Defined.") +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_secs(1)) { + if let Err(err) = watcher.publish(dto.topic.as_str(), dto.message.as_bytes()) { + warn!("Can not publish message to MQTT. {}", err); + } + } + }); + anyhow::Ok(()) } } -#[derive(Copy, Debug, Clone, Serialize, Deserialize)] -pub struct MqDto<'a, 'b> { - pub topic: &'a str, - pub message: &'b [u8], -} - -impl EspTypedEventSource for MqDto<'_, '_> { - fn source() -> *const c_types::c_char { - b"MqDto\0".as_ptr() as *const _ - } -} - -impl EspTypedEventSerializer> for MqDto<'_, '_> { - fn serialize(event: &MqDto, f: impl for<'a> FnOnce(&'a EspEventPostData) -> R) -> R { - f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) }) - } -} - -impl<'b, 'c> EspTypedEventDeserializer> for MqDto<'b, 'c> { - fn deserialize( - data: &EspEventFetchData, - f: &mut impl for<'a> FnMut(&'a MqDto<'b, 'c>) -> R, - ) -> R { - f(unsafe { data.as_payload() }) - } +#[derive(Debug, Clone)] +pub struct MqDto { + pub message: String, + pub topic: String, } From 13a1b99cb540be75c8f8e5a99e53f5531a5e1c0a Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sat, 10 Sep 2022 16:10:13 +0800 Subject: [PATCH 10/20] =?UTF-8?q?fix:=20Beep=20=E6=9C=89=E6=A6=82=E7=8E=87?= =?UTF-8?q?=E5=B4=A9=E6=BA=83=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/beep.rs | 57 +++++++++++++++++++++++++++----------------- src/eventloop.rs | 0 src/main.rs | 3 +-- src/message_queue.rs | 5 +--- 4 files changed, 37 insertions(+), 28 deletions(-) delete mode 100644 src/eventloop.rs diff --git a/src/beep.rs b/src/beep.rs index 82f7b59..7ab55b8 100644 --- a/src/beep.rs +++ b/src/beep.rs @@ -4,9 +4,9 @@ use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; use esp_idf_hal::ledc::{CHANNEL0, TIMER0}; use esp_idf_hal::prelude::*; use esp_idf_svc::timer::{EspTimer, EspTimerService}; -use esp_idf_sys::EspError; +use esp_idf_sys::{ledc_mode_t_LEDC_LOW_SPEED_MODE, ledc_set_freq, EspError}; use log::{info, warn}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; type LedcChannel = Channel, P>; @@ -28,16 +28,28 @@ impl BeepState { } } +type BeepChannel = Channel, Gpio4>; + pub struct Beep { watch_timer: Option, state: BeepState, + channel: Arc>, } impl Beep { - pub fn new() -> Self { - return 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: Gpio4, @@ -58,40 +70,37 @@ impl Beep { return Ok(()); } let state = Arc::new(Mutex::new(self.state)); - let mut timer = EspTimerService::new()?.timer(move || { - match state.lock().as_mut() { + 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) { + 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))?; + }) + .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) -> 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() }; + 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]; - let mut channel = Self::init_channel(pin, hw_timer, hw_channel, if curr < 1000 { 1000.Hz().into()} else {curr.Hz().into()} ) - .map_err(|err| anyhow::anyhow!("Failed to initialize channel. {}", err))?; - - if curr < 1000 { + 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"); } @@ -105,7 +114,11 @@ impl Beep { pub mod ringtone { 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 = [2000, 1950, 0, 0, 1980, 1900, 0, 0, 2000, 1900, 0, 0, 1980, 1900, 0, 0]; + pub const BATTERY_LOW: Type = [ + 2000, 1950, 0, 0, 1980, 1900, 0, 0, 2000, 1900, 0, 0, 1980, 1900, 0, 0, + ]; pub const SILENCE: Type = [0; 16]; - pub const SHUTDOWN: Type = [3450, 3500, 0, 3500, 3050, 3000, 0, 3000, 3050, 1000, 1000, 1000, 0, 0, 0, 0]; + pub const SHUTDOWN: Type = [ + 3450, 3500, 0, 3500, 3050, 3000, 0, 3000, 3050, 1000, 1000, 1000, 0, 0, 0, 0, + ]; } diff --git a/src/eventloop.rs b/src/eventloop.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.rs b/src/main.rs index ad82b7e..bf05b10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,6 @@ fn main() { let blink_pin = peripherals.pins.gpio5; - info!("About to start a background event loop"); match EspBackgroundEventLoop::new(&Default::default()) { Ok(eventloop) => unsafe { VOLTAGE_EVENTLOOP = Some(eventloop) }, Err(err) => error!("Init Event Loop failed. {:?}", err), @@ -72,7 +71,7 @@ fn main() { sleep(Duration::from_millis(100)); - let mut beep = Beep::new(); + let mut beep = Beep::new().unwrap(); let mut dc_out_controller = DcOutController::get_instance().expect("Can not get DcOutController instance"); diff --git a/src/message_queue.rs b/src/message_queue.rs index 5c95911..71f175b 100644 --- a/src/message_queue.rs +++ b/src/message_queue.rs @@ -9,10 +9,7 @@ use std::{ use anyhow::Result; use embedded_svc::mqtt::client::{utils::ConnState, Client, Connection, MessageImpl, Publish, QoS}; -use esp_idf_svc::{ - eventloop::{Background, EspEventLoop}, - mqtt::client::{EspMqttClient, MqttClientConfiguration}, -}; +use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration}; use esp_idf_sys::EspError; use log::*; From 7d7048b671ce2689a4947891ef099f3d5310057f Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sat, 10 Sep 2022 17:35:10 +0800 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20DC=20OUT=20?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E4=BF=A1=E5=8F=B7=E8=BE=93=E5=87=BA=E6=8A=96?= =?UTF-8?q?=E5=8A=A8=E3=80=82Closed=20#4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dc_out_controller.rs | 102 ++++++++++++++++++++------------------- src/main.rs | 2 +- 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index 43e0a84..3f14c06 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -1,4 +1,8 @@ -use std::sync::{Arc, Mutex}; +use std::{ + sync::{ + Arc, Mutex, MutexGuard, + }, +}; use embedded_hal::digital::v2::{OutputPin, PinState}; use embedded_svc::event_bus::{EventBus, Postbox}; @@ -11,7 +15,7 @@ use esp_idf_sys::c_types; use log::{info, warn}; use serde_json::json; -use crate::{voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}}; +use crate::voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; const WAITING_OFF_SECONDS: u8 = 60; @@ -132,8 +136,7 @@ impl DcOutControllerState { DcOutStatus::TurningOff(seconds) => seconds.into(), _ => -1, }; - json!({ "status": status, "pin_state": pin_state, "seconds": seconds }) - .to_string() + json!({ "status": status, "pin_state": pin_state, "seconds": seconds }).to_string() } } @@ -161,67 +164,67 @@ impl EspTypedEventDeserializer for DcOutControllerState { } } +type DcOutPin = Gpio6; + pub struct DcOutController { pub state: DcOutControllerState, voltage_subscription: Option>>, + pin: Arc>, } impl DcOutController { - fn new() -> Self { - Self { - state: DcOutControllerState::new(), - voltage_subscription: None, - } - } + pub fn new() -> anyhow::Result { + let mut pin = unsafe { Gpio6::::new() } + .into_output() + .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; - pub fn get_instance() -> anyhow::Result { - // if let Some(instance) = unsafe { &mut INSTANCE } { - // anyhow::Ok(*instance) - // } else { - let instance = Self::new(); - // unsafe { INSTANCE = Some(instance) }; 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(instance) - // } + + anyhow::Ok(Self { + state: DcOutControllerState::new(), + voltage_subscription: None, + pin: Arc::new(Mutex::new(pin)), + }) } pub fn watch(&mut self) -> anyhow::Result<()> { - let state = Arc::new(Mutex::new(self.state)); + 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.lock().as_mut() { - Ok(state) => { - if obj.adapter_voltage < 1000 { - if obj.battery_voltage < 1000 { - state.turn_off(); - } else { - state.handle_adapter_down(); - } - } else { - state.turn_on(); - } - let ref state = **state; - if let Err(err) = Self::output_ctl(*state) { + .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(state, pin) { warn!("Put Control Pin State Failed. {}", err); } - - 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"); - } - info!("status: {:?}", state); } - Err(err) => warn!("Can not lock state. {}", 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"); + } + info!("status: {:?}", state); + }) .map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?; self.voltage_subscription = Some(voltage_subscription); } else { @@ -229,11 +232,10 @@ impl DcOutController { } anyhow::Ok(()) } - fn output_ctl(mut state: DcOutControllerState) -> anyhow::Result<()> { - let mut pin = unsafe { Gpio6::::new() } - .into_output() - .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; - + fn output_ctl( + mut state: DcOutControllerState, + mut pin: MutexGuard, + ) -> anyhow::Result<()> { if DcOutStatus::Off == state.status && state.pin_state == PinState::Low { pin.set_high() .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin High Failed. {}", err))?; diff --git a/src/main.rs b/src/main.rs index bf05b10..40249ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,7 +74,7 @@ fn main() { let mut beep = Beep::new().unwrap(); let mut dc_out_controller = - DcOutController::get_instance().expect("Can not get DcOutController instance"); + DcOutController::new().expect("Can not get DcOutController instance"); dc_out_controller .watch() .expect("Can not watch for dc_out_controller"); From 98751ffc9cae6b053f1fdf4b0a15a03f4ec8d9cb Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sat, 10 Sep 2022 17:43:38 +0800 Subject: [PATCH 12/20] style: clean up. --- src/dc_out_controller.rs | 7 +++---- src/main.rs | 20 ++++++-------------- src/time.rs | 3 +-- src/voltage_detection.rs | 6 ++---- 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index 3f14c06..8487490 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -12,7 +12,7 @@ use esp_idf_svc::eventloop::{ EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User, }; use esp_idf_sys::c_types; -use log::{info, warn}; +use log::{info, warn, debug}; use serde_json::json; use crate::voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; @@ -22,7 +22,6 @@ const WAITING_OFF_SECONDS: u8 = 60; pub static mut DC_OUT_STATE_EVENT_LOOP: Option< EspEventLoop>, > = None; -static mut INSTANCE: Option = None; #[derive(Debug, Clone, Copy, PartialEq)] pub enum DcOutStatus { @@ -174,7 +173,7 @@ pub struct DcOutController { impl DcOutController { pub fn new() -> anyhow::Result { - let mut pin = unsafe { Gpio6::::new() } + let pin = unsafe { Gpio6::::new() } .into_output() .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; @@ -223,7 +222,7 @@ impl DcOutController { } else { warn!("DC_OUT_STATE_EVENT_LOOP is None"); } - info!("status: {:?}", state); + debug!("status: {:?}", state); }) .map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?; self.voltage_subscription = Some(voltage_subscription); diff --git a/src/main.rs b/src/main.rs index 40249ec..54de644 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,9 @@ -use embedded_svc::event_bus::{EventBus, Postbox}; -use esp_idf_svc::eventloop::{Background, EspBackgroundEventLoop, EspEventLoop}; +use embedded_svc::event_bus::{EventBus}; +use esp_idf_svc::eventloop::{EspBackgroundEventLoop}; use esp_idf_sys::{self as _}; use log::*; use std::{ - borrow::Borrow, env, - sync::{Arc, Mutex}, thread::{self, sleep}, time::Duration, }; @@ -105,11 +103,8 @@ fn main() { topic: "voltage".to_string(), message: json_str, }); - match result { - Ok(()) => { - warn!("send voltage to mq message failed.") - } - Err(err) => warn!("send voltage to mq message failed. {}", err), + if let Err(err) = result { + warn!("send voltage to mq message failed. {}", err) } } Err(err) => warn!("send voltage to mq message failed. {}", err), @@ -145,11 +140,8 @@ fn main() { message: message.to_json(), }); - match result { - Ok(_) => { - info!("send dc_out_state message success.") - } - Err(err) => warn!("send dc_out_state message failed. {}", err), + 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), diff --git a/src/time.rs b/src/time.rs index 93a2d73..baf1a36 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,8 +1,7 @@ -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 log::{info}; use std::time::{Duration}; pub struct Time { diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index 3e2caad..44501c5 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -23,7 +23,7 @@ use esp_idf_svc::{ timer::{EspTimer, EspTimerService}, }; use esp_idf_sys::c_types; -use log::{info, warn}; +use log::{warn, debug}; use serde::{Deserialize, Serialize}; pub static mut VOLTAGE_EVENTLOOP: Option>> = None; @@ -64,7 +64,7 @@ impl VoltageDetection { if let Err(err) = worker.read_once() { warn!("Read Failed. {}", err); } - info!( + debug!( "Adapter: {},\tBattery: {},\t Output: {}", worker.adapter_voltage, worker.battery_voltage, worker.output_voltage ); @@ -78,8 +78,6 @@ impl VoltageDetection { None, ) { warn!("Post Result to Event Loop failed. {}", err); - } else { - info!("Event Loop Post"); } } else { warn!("EVENTLOOP IS NONE"); From d1cda50629f1e2884968d02ec8cbd89fbdcc471d Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sat, 10 Sep 2022 18:28:29 +0800 Subject: [PATCH 13/20] doc: README. --- README.md | 21 +++++++++++++++++++++ src/main.rs | 1 - 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..fec0593 --- /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 0`; +- `GPIO 2`:电池电芯电压检测,使用 `ADC 0`; +- `GPIO 3`:UPS 输出电压检测,使用 `ADC 0`; +- `GPIO 4`:蜂鸣器模拟信号输出,使用 `CHANNEL 0`, `TIMER 0`; +- `GPIO 5`:工作状态指示灯信号输出; +- `GPIO 6`:UPS 输出控制信号,适用于 P-MOS 开关; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 54de644..74c27b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,7 +120,6 @@ fn main() { 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| { - info!("Event Loop Value"); match message.status { dc_out_controller::DcOutStatus::WaitingOff => { beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep.") From 281ea38d04eb8483ee8c29c199c8a9ce382f398e Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 11 Sep 2022 16:49:13 +0800 Subject: [PATCH 14/20] =?UTF-8?q?feat:=20=E9=81=BF=E5=85=8D=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=88=9D=E5=A7=8B=E5=8C=96=20ADC=20=E7=AE=A1=E8=84=9A?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- src/charge_controller.rs | 249 +++++++++++++++++++++++++++++++++++++++ src/dc_out_controller.rs | 2 +- src/main.rs | 2 +- src/message_queue.rs | 3 +- src/voltage_detection.rs | 113 +++++++++++------- 6 files changed, 324 insertions(+), 51 deletions(-) create mode 100644 src/charge_controller.rs diff --git a/README.md b/README.md index fec0593..71603e9 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ ## GPIO 定义 -- `GPIO 1`:UPS 输入电压检测,使用 `ADC 0`; -- `GPIO 2`:电池电芯电压检测,使用 `ADC 0`; -- `GPIO 3`:UPS 输出电压检测,使用 `ADC 0`; +- `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/src/charge_controller.rs b/src/charge_controller.rs new file mode 100644 index 0000000..3c4c222 --- /dev/null +++ b/src/charge_controller.rs @@ -0,0 +1,249 @@ +use std::{ + sync::{ + Arc, Mutex, MutexGuard, + }, +}; + +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::{info, warn, debug}; +use serde_json::json; + +use crate::voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; + +const WAITING_OFF_SECONDS: u8 = 60; + +pub static mut DC_OUT_STATE_EVENT_LOOP: Option< + EspEventLoop>, +> = None; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DcOutStatus { + WaitingOn(u8), + On, + Off, + WaitingOff, + TurningOff(u8), +} + +#[derive(Debug, Clone, Copy)] +pub struct DcOutControllerState { + pub status: DcOutStatus, + pub pin_state: PinState, +} + +impl DcOutControllerState { + pub fn new() -> Self { + Self { + status: DcOutStatus::On, + pin_state: PinState::Low, + } + } + + fn handle_adapter_down(&mut self) { + info!("status: {:?}", self); + match self.status { + DcOutStatus::On => { + self.status = DcOutStatus::WaitingOff; + } + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + self.status = DcOutStatus::Off; + } + } + _ => {} + }; + } + + fn turn_off(&mut self) { + match self.status { + DcOutStatus::On => { + self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOff => { + self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + self.status = DcOutStatus::Off; + } + } + _ => {} + }; + } + + fn turn_on(&mut self) { + match self.status { + DcOutStatus::WaitingOff => { + self.status = DcOutStatus::On; + } + DcOutStatus::Off => { + self.status = DcOutStatus::WaitingOn(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOn(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::WaitingOn(seconds); + } else { + self.status = DcOutStatus::On; + } + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + 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 seconds: i16 = match self.status { + DcOutStatus::WaitingOn(seconds) => seconds.into(), + DcOutStatus::TurningOff(seconds) => seconds.into(), + _ => -1, + }; + json!({ "status": status, "pin_state": pin_state, "seconds": seconds }).to_string() + } +} + +impl EspTypedEventSource for DcOutControllerState { + fn source() -> *const c_types::c_char { + b"Charge\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(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"); + } + debug!("status: {:?}", state); + }) + .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( + mut state: DcOutControllerState, + mut pin: MutexGuard, + ) -> anyhow::Result<()> { + if DcOutStatus::Off == state.status && state.pin_state == PinState::Low { + 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 && state.pin_state == PinState::High { + 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 8487490..98e9020 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -141,7 +141,7 @@ impl DcOutControllerState { impl EspTypedEventSource for DcOutControllerState { fn source() -> *const c_types::c_char { - b"VOLTAGES\0".as_ptr() as *const _ + b"DcOut\0".as_ptr() as *const _ } } diff --git a/src/main.rs b/src/main.rs index 74c27b4..02c7b81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,7 @@ fn main() { let mut voltage_detection = VoltageDetection::new(); - voltage_detection + voltage_detection.unwrap() .watching() .expect("Can not watch voltages."); diff --git a/src/message_queue.rs b/src/message_queue.rs index 71f175b..6155db4 100644 --- a/src/message_queue.rs +++ b/src/message_queue.rs @@ -95,11 +95,12 @@ impl MessageQueue { 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_secs(1)) { + 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(()) } diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index 44501c5..fdcd0ea 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -1,32 +1,32 @@ 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, - timer::{PeriodicTimer, TimerService}, -}; +use embedded_svc::{event_bus::Postbox}; use esp_idf_hal::{ adc::{ config::{self}, - Analog, PoweredAdc, ADC1, + Analog, Atten6dB, PoweredAdc, ADC1, }, gpio::{Gpio1, Gpio2, Gpio3, Input}, }; use esp_idf_svc::{ eventloop::{ - EspEventFetchData, EspEventPostData, EspTypedEventDeserializer, EspTypedEventSerializer, - EspTypedEventSource, EspEventLoop, Background, EspBackgroundEventLoop, + Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, + EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, }, - timer::{EspTimer, EspTimerService}, }; use esp_idf_sys::c_types; -use log::{warn, debug}; +use log::{debug, warn}; use serde::{Deserialize, Serialize}; -pub static mut VOLTAGE_EVENTLOOP: Option>> = None; +use crate::time::Time; + +pub static mut VOLTAGE_EVENTLOOP: Option>> = + None; const ADAPTER_OFFSET: f32 = 12002f32 / 900f32; const BATTERY_OFFSET: f32 = 12002f32 / 900f32; @@ -34,7 +34,6 @@ const OUTPUT_OFFSET: f32 = 12002f32 / 900f32; pub struct VoltageDetection { pub worker: VoltageDetectionWorker, - watch_timer: Option, } #[derive(Clone, Copy, Serialize, Deserialize)] @@ -45,11 +44,10 @@ pub struct VoltageDetectionWorker { } impl VoltageDetection { - pub fn new() -> Self { - return Self { + pub fn new() -> anyhow::Result { + return anyhow::Ok(Self { worker: VoltageDetectionWorker::new(), - watch_timer: None, - }; + }); } pub fn watching(&mut self) -> anyhow::Result<()> { @@ -58,16 +56,41 @@ impl VoltageDetection { Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), } let worker = Arc::new(Mutex::new(self.worker)); - let mut timer = EspTimerService::new()?.timer(move || { - match worker.lock().as_mut() { - Ok(worker) => { - if let Err(err) = worker.read_once() { - warn!("Read Failed. {}", err); + + 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 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 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), } - debug!( - "Adapter: {},\tBattery: {},\t Output: {}", - worker.adapter_voltage, worker.battery_voltage, worker.output_voltage - ); + if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { if let Err(err) = eventloop.post( &mut VoltageDetectionWorker { @@ -82,15 +105,20 @@ impl VoltageDetection { } else { warn!("EVENTLOOP IS NONE"); } - } - Err(err) => { - warn!("Lock VoltageDetection Worker Failed. {}", err) - } - } - })?; - timer.every(Duration::from_secs(1))?; + let mut delta = Time::new().get_time() - last_runing_at; - self.watch_timer = Some(timer); + if delta >= Duration::from_millis(1000) { + delta = Duration::ZERO + } + + sleep(Duration::from_millis(1000) - delta); + } + }; + + if let Err(err) = handler() { + warn!("init failed. {}", err) + } + }); return anyhow::Ok(()); } } @@ -104,10 +132,12 @@ impl VoltageDetectionWorker { }; } - pub fn read_once(&mut self) -> anyhow::Result<()> { - let adapter_pin = unsafe { Gpio1::::new() } - .into_analog_atten_6db() - .map_err(|err| anyhow::anyhow!("Failed to set GPIO 1 as analog input. {}", err))?; + pub fn read_once( + &mut self, + adapter_pin: &mut Gpio1>, + battery_pin: &mut Gpio2>, + output_pin: &mut Gpio3>, + ) -> anyhow::Result<()> { match self.read_pin_once(adapter_pin) { Ok(voltage) => { self.adapter_voltage = ((voltage as f32) * ADAPTER_OFFSET) as u16; @@ -117,9 +147,6 @@ impl VoltageDetectionWorker { } } - let battery_pin = unsafe { Gpio2::::new() } - .into_analog_atten_6db() - .map_err(|err| anyhow::anyhow!("Failed to set GPIO 2 as analog input. {}", err))?; match self.read_pin_once(battery_pin) { Ok(voltage) => { self.battery_voltage = ((voltage as f32) * BATTERY_OFFSET) as u16; @@ -128,10 +155,6 @@ impl VoltageDetectionWorker { warn!("Adapter Voltage read failed: {:?}", err); } } - - let output_pin = unsafe { Gpio3::::new() } - .into_analog_atten_6db() - .map_err(|err| anyhow::anyhow!("Failed to set GPIO 3 as analog input. {}", err))?; match self.read_pin_once(output_pin) { Ok(voltage) => { self.output_voltage = ((voltage as f32) * OUTPUT_OFFSET) as u16; @@ -145,10 +168,10 @@ impl VoltageDetectionWorker { } pub fn read_pin_once, PIN: Channel>( &mut self, - mut pin: PIN, + pin: &mut PIN, ) -> anyhow::Result { let mut adc = PoweredAdc::new(unsafe { ADC1::new() }, config::Config::new())?; - let voltage = adc.read(&mut pin); + let voltage = adc.read(pin); match voltage { Ok(voltage) => anyhow::Ok(voltage), Err(err) => { From 2f52f142b88d140fdf6e8a2f63e2d18e91d11b66 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 11 Sep 2022 21:31:36 +0800 Subject: [PATCH 15/20] =?UTF-8?q?feat:=20=E5=85=85=E7=94=B5=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/charge_controller.rs | 179 +++++++++++---------------------------- src/main.rs | 35 +++++++- 2 files changed, 81 insertions(+), 133 deletions(-) diff --git a/src/charge_controller.rs b/src/charge_controller.rs index 3c4c222..831e82f 100644 --- a/src/charge_controller.rs +++ b/src/charge_controller.rs @@ -1,189 +1,100 @@ -use std::{ - sync::{ - Arc, Mutex, MutexGuard, - }, -}; +use std::sync::{Arc, Mutex, MutexGuard}; use embedded_hal::digital::v2::{OutputPin, PinState}; use embedded_svc::event_bus::{EventBus, Postbox}; -use esp_idf_hal::gpio::{Gpio6, Output}; +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::{info, warn, debug}; +use log::{warn}; use serde_json::json; use crate::voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; -const WAITING_OFF_SECONDS: u8 = 60; - -pub static mut DC_OUT_STATE_EVENT_LOOP: Option< +pub static mut CHARGE_STATE_EVENT_LOOP: Option< EspEventLoop>, > = None; #[derive(Debug, Clone, Copy, PartialEq)] -pub enum DcOutStatus { - WaitingOn(u8), - On, - Off, - WaitingOff, - TurningOff(u8), +pub enum ChargeStatus { + Charging, + Charged, } #[derive(Debug, Clone, Copy)] -pub struct DcOutControllerState { - pub status: DcOutStatus, +pub struct ChargeControllerState { + pub status: ChargeStatus, pub pin_state: PinState, } -impl DcOutControllerState { +impl ChargeControllerState { pub fn new() -> Self { Self { - status: DcOutStatus::On, + status: ChargeStatus::Charging, pin_state: PinState::Low, } } - fn handle_adapter_down(&mut self) { - info!("status: {:?}", self); - match self.status { - DcOutStatus::On => { - self.status = DcOutStatus::WaitingOff; - } - DcOutStatus::WaitingOn(_) => { - self.status = DcOutStatus::Off; - } - DcOutStatus::TurningOff(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::TurningOff(seconds); - } else { - self.status = DcOutStatus::Off; - } - } - _ => {} - }; - } - - fn turn_off(&mut self) { - match self.status { - DcOutStatus::On => { - self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); - } - DcOutStatus::WaitingOff => { - self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); - } - DcOutStatus::WaitingOn(_) => { - self.status = DcOutStatus::Off; - } - DcOutStatus::TurningOff(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::TurningOff(seconds); - } else { - self.status = DcOutStatus::Off; - } - } - _ => {} - }; - } - - fn turn_on(&mut self) { - match self.status { - DcOutStatus::WaitingOff => { - self.status = DcOutStatus::On; - } - DcOutStatus::Off => { - self.status = DcOutStatus::WaitingOn(WAITING_OFF_SECONDS); - } - DcOutStatus::WaitingOn(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::WaitingOn(seconds); - } else { - self.status = DcOutStatus::On; - } - } - DcOutStatus::TurningOff(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::TurningOff(seconds); - } else { - 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", + ChargeStatus::Charging => "Charging", + ChargeStatus::Charged => "Charged", }; let pin_state = match self.pin_state { PinState::Low => "Low", PinState::High => "High", }; - let seconds: i16 = match self.status { - DcOutStatus::WaitingOn(seconds) => seconds.into(), - DcOutStatus::TurningOff(seconds) => seconds.into(), - _ => -1, - }; - json!({ "status": status, "pin_state": pin_state, "seconds": seconds }).to_string() + json!({ "status": status, "pin_state": pin_state}).to_string() } } -impl EspTypedEventSource for DcOutControllerState { +impl EspTypedEventSource for ChargeControllerState { fn source() -> *const c_types::c_char { b"Charge\0".as_ptr() as *const _ } } -impl EspTypedEventSerializer for DcOutControllerState { +impl EspTypedEventSerializer for ChargeControllerState { fn serialize( - event: &DcOutControllerState, + event: &ChargeControllerState, f: impl for<'a> FnOnce(&'a EspEventPostData) -> R, ) -> R { f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) }) } } -impl EspTypedEventDeserializer for DcOutControllerState { +impl EspTypedEventDeserializer for ChargeControllerState { fn deserialize( data: &EspEventFetchData, - f: &mut impl for<'a> FnMut(&'a DcOutControllerState) -> R, + f: &mut impl for<'a> FnMut(&'a ChargeControllerState) -> R, ) -> R { f(unsafe { data.as_payload() }) } } -type DcOutPin = Gpio6; +type ChargeCtlPin = Gpio7; -pub struct DcOutController { - pub state: DcOutControllerState, +pub struct ChargeController { + pub state: ChargeControllerState, voltage_subscription: Option>>, - pin: Arc>, + pin: Arc>, } -impl DcOutController { +impl ChargeController { pub fn new() -> anyhow::Result { - let pin = unsafe { Gpio6::::new() } + 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 { DC_OUT_STATE_EVENT_LOOP = Some(eventloop) }, + Ok(eventloop) => unsafe { CHARGE_STATE_EVENT_LOOP = Some(eventloop) }, Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), } anyhow::Ok(Self { - state: DcOutControllerState::new(), + state: ChargeControllerState::new(), voltage_subscription: None, pin: Arc::new(Mutex::new(pin)), }) @@ -196,33 +107,39 @@ impl DcOutController { 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(); + match state.status { + ChargeStatus::Charging => { + if obj.battery_voltage < 12600 { + state.status = ChargeStatus::Charging; + } else { + state.status = ChargeStatus::Charged; + } + } + ChargeStatus::Charged => { + if obj.battery_voltage < 10500 { + state.status = ChargeStatus::Charging; + } else { + state.status = ChargeStatus::Charged; + } } - } else { - state.turn_on(); } match pin.lock() { Ok(pin) => { - if let Err(err) = Self::output_ctl(state, 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 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!("DC_OUT_STATE_EVENT_LOOP is None"); + warn!("CHARGE_STATE_EVENT_LOOP is None"); } - debug!("status: {:?}", state); }) .map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?; self.voltage_subscription = Some(voltage_subscription); @@ -232,14 +149,14 @@ impl DcOutController { anyhow::Ok(()) } fn output_ctl( - mut state: DcOutControllerState, - mut pin: MutexGuard, + state: &mut ChargeControllerState, + mut pin: MutexGuard, ) -> anyhow::Result<()> { - if DcOutStatus::Off == state.status && state.pin_state == PinState::Low { + 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 DcOutStatus::On == state.status && 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; diff --git a/src/main.rs b/src/main.rs index 02c7b81..76e4e02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,12 +15,13 @@ 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}, + 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, @@ -56,7 +57,7 @@ fn main() { blink.play(); }); - let mut voltage_detection = VoltageDetection::new(); + let voltage_detection = VoltageDetection::new(); voltage_detection.unwrap() .watching() @@ -77,6 +78,12 @@ fn main() { .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(); @@ -91,6 +98,7 @@ fn main() { 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() } { @@ -151,6 +159,29 @@ fn main() { 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 { sleep(Duration::from_millis(100)); } From 9b40b5dfdde7d19115f6b42b817b7ccb133a702e Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 11 Sep 2022 21:37:50 +0800 Subject: [PATCH 16/20] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20UPS=20?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E7=AE=A1=E8=84=9A=E7=8A=B6=E6=80=81=E6=9C=AA?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dc_out_controller.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index 98e9020..14edd85 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -47,7 +47,6 @@ impl DcOutControllerState { } fn handle_adapter_down(&mut self) { - info!("status: {:?}", self); match self.status { DcOutStatus::On => { self.status = DcOutStatus::WaitingOff; @@ -208,7 +207,7 @@ impl DcOutController { match pin.lock() { Ok(pin) => { - if let Err(err) = Self::output_ctl(state, pin) { + if let Err(err) = Self::output_ctl(&mut state, pin) { warn!("Put Control Pin State Failed. {}", err); } } @@ -222,7 +221,6 @@ impl DcOutController { } else { warn!("DC_OUT_STATE_EVENT_LOOP is None"); } - debug!("status: {:?}", state); }) .map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?; self.voltage_subscription = Some(voltage_subscription); @@ -232,14 +230,14 @@ impl DcOutController { anyhow::Ok(()) } fn output_ctl( - mut state: DcOutControllerState, + state: &mut DcOutControllerState, mut pin: MutexGuard, ) -> anyhow::Result<()> { - if DcOutStatus::Off == state.status && state.pin_state == PinState::Low { + 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 && 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; From 711306f2c1bf4f9406a56a08518399cece7adcbc Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Mon, 12 Sep 2022 09:39:28 +0800 Subject: [PATCH 17/20] style: clean up. --- src/dc_out_controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index 14edd85..984e5e4 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -12,7 +12,7 @@ use esp_idf_svc::eventloop::{ EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User, }; use esp_idf_sys::c_types; -use log::{info, warn, debug}; +use log::{warn}; use serde_json::json; use crate::voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; From 2aaa85af71cc9485d690c0b3021b45c83fcb4e69 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Mon, 12 Sep 2022 10:13:59 +0800 Subject: [PATCH 18/20] =?UTF-8?q?fix:=20=E7=94=B5=E5=8E=8B=E9=87=87?= =?UTF-8?q?=E6=A0=B7=E6=AF=8F=E6=AC=A1=E9=87=87=E6=A0=B710=E6=AC=A1?= =?UTF-8?q?=E5=8F=96=E5=B9=B3=E5=9D=87=E5=80=BC=E3=80=82Close=20#5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/voltage_detection.rs | 51 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index fdcd0ea..f122cad 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -5,7 +5,7 @@ use std::{ }; use embedded_hal::{adc::Channel, prelude::_embedded_hal_adc_OneShot}; -use embedded_svc::{event_bus::Postbox}; +use embedded_svc::event_bus::Postbox; use esp_idf_hal::{ adc::{ config::{self}, @@ -13,11 +13,9 @@ use esp_idf_hal::{ }, gpio::{Gpio1, Gpio2, Gpio3, Input}, }; -use esp_idf_svc::{ - eventloop::{ - Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, - EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, - }, +use esp_idf_svc::eventloop::{ + Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, + EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, }; use esp_idf_sys::c_types; use log::{debug, warn}; @@ -75,6 +73,8 @@ impl VoltageDetection { 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) })?; @@ -83,7 +83,12 @@ impl VoltageDetection { loop { last_runing_at = Time::new().get_time(); - match worker.read_once(&mut adapter_pin, &mut battery_pin, &mut output_pin) { + 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 @@ -107,11 +112,11 @@ impl VoltageDetection { } let mut delta = Time::new().get_time() - last_runing_at; - if delta >= Duration::from_millis(1000) { + if delta >= Duration::from_millis(5000) { delta = Duration::ZERO } - sleep(Duration::from_millis(1000) - delta); + sleep(Duration::from_millis(5000) - delta); } }; @@ -134,11 +139,12 @@ impl VoltageDetectionWorker { 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(adapter_pin) { + match self.read_pin_once(adc, adapter_pin) { Ok(voltage) => { self.adapter_voltage = ((voltage as f32) * ADAPTER_OFFSET) as u16; } @@ -147,7 +153,7 @@ impl VoltageDetectionWorker { } } - match self.read_pin_once(battery_pin) { + match self.read_pin_once(adc, battery_pin) { Ok(voltage) => { self.battery_voltage = ((voltage as f32) * BATTERY_OFFSET) as u16; } @@ -155,7 +161,7 @@ impl VoltageDetectionWorker { warn!("Adapter Voltage read failed: {:?}", err); } } - match self.read_pin_once(output_pin) { + match self.read_pin_once(adc, output_pin) { Ok(voltage) => { self.output_voltage = ((voltage as f32) * OUTPUT_OFFSET) as u16; } @@ -168,16 +174,23 @@ impl VoltageDetectionWorker { } pub fn read_pin_once, PIN: Channel>( &mut self, + adc: &mut PoweredAdc, pin: &mut PIN, ) -> anyhow::Result { - let mut adc = PoweredAdc::new(unsafe { ADC1::new() }, config::Config::new())?; - let voltage = adc.read(pin); - match voltage { - Ok(voltage) => anyhow::Ok(voltage), - Err(err) => { - anyhow::bail!("Adapter Voltage read failed: {:?}", err) - } + 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) } } From 09ae17fa31d72b88759157fe3edd7b2e9e16bb6a Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Mon, 12 Sep 2022 11:49:52 +0800 Subject: [PATCH 19/20] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=AD=89?= =?UTF-8?q?=E5=BE=85=E8=BE=93=E5=87=BA=E5=BC=80=E5=90=AF=E5=92=8C=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E7=9A=84=E5=80=92=E8=AE=A1=E6=97=B6=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dc_out_controller.rs | 74 +++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index 984e5e4..86a0200 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -1,7 +1,6 @@ use std::{ - sync::{ - Arc, Mutex, MutexGuard, - }, + sync::{Arc, Mutex, MutexGuard}, + time::Duration, }; use embedded_hal::digital::v2::{OutputPin, PinState}; @@ -12,12 +11,16 @@ use esp_idf_svc::eventloop::{ EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User, }; use esp_idf_sys::c_types; -use log::{warn}; +use log::warn; use serde_json::json; -use crate::voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; +use crate::{ + time::Time, + voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, +}; -const WAITING_OFF_SECONDS: u8 = 60; +const WAITING_OFF_DURATION: u64 = 60; +const WAITING_ON_DURATION: u64 = 60; pub static mut DC_OUT_STATE_EVENT_LOOP: Option< EspEventLoop>, @@ -25,11 +28,11 @@ pub static mut DC_OUT_STATE_EVENT_LOOP: Option< #[derive(Debug, Clone, Copy, PartialEq)] pub enum DcOutStatus { - WaitingOn(u8), + WaitingOn(Duration), On, Off, WaitingOff, - TurningOff(u8), + TurningOff(Duration), } #[derive(Debug, Clone, Copy)] @@ -54,11 +57,9 @@ impl DcOutControllerState { DcOutStatus::WaitingOn(_) => { self.status = DcOutStatus::Off; } - DcOutStatus::TurningOff(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::TurningOff(seconds); - } else { + DcOutStatus::TurningOff(target) => { + let now = Time::new().get_time(); + if now > target { self.status = DcOutStatus::Off; } } @@ -67,21 +68,21 @@ impl DcOutControllerState { } fn turn_off(&mut self) { + let now = Time::new().get_time(); match self.status { DcOutStatus::On => { - self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + self.status = + DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION)); } DcOutStatus::WaitingOff => { - self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + self.status = + DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION)); } DcOutStatus::WaitingOn(_) => { self.status = DcOutStatus::Off; } - DcOutStatus::TurningOff(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::TurningOff(seconds); - } else { + DcOutStatus::TurningOff(target) => { + if target < now { self.status = DcOutStatus::Off; } } @@ -90,26 +91,22 @@ impl DcOutControllerState { } 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(WAITING_OFF_SECONDS); + self.status = + DcOutStatus::WaitingOn(now + Duration::from_secs(WAITING_ON_DURATION)); } - DcOutStatus::WaitingOn(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::WaitingOn(seconds); - } else { + DcOutStatus::WaitingOn(target) => { + if target <= now { self.status = DcOutStatus::On; } } - DcOutStatus::TurningOff(seconds) => { - let seconds = seconds - 1; - if seconds > 0 { - self.status = DcOutStatus::TurningOff(seconds); - } else { + DcOutStatus::TurningOff(target) => { + if target <= now { self.status = DcOutStatus::On; } } @@ -129,11 +126,18 @@ impl DcOutControllerState { PinState::Low => "Low", PinState::High => "High", }; - let seconds: i16 = match self.status { - DcOutStatus::WaitingOn(seconds) => seconds.into(), - DcOutStatus::TurningOff(seconds) => seconds.into(), - _ => -1, + 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() } } From daf6effe59800ff40fda3da399472f0ab47b64d7 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Mon, 12 Sep 2022 11:50:26 +0800 Subject: [PATCH 20/20] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=85=85?= =?UTF-8?q?=E7=94=B5=E6=8E=A7=E5=88=B6=E5=80=92=E8=AE=A1=E6=97=B6=E4=B8=8A?= =?UTF-8?q?=E6=8A=A5=E5=88=B0=20MQTT=20=E8=83=BD=E5=8A=9B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/charge_controller.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/charge_controller.rs b/src/charge_controller.rs index 831e82f..9b58ee8 100644 --- a/src/charge_controller.rs +++ b/src/charge_controller.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, Mutex, MutexGuard}; +use std::{ + sync::{Arc, Mutex, MutexGuard}, + time::Duration, +}; use embedded_hal::digital::v2::{OutputPin, PinState}; use embedded_svc::event_bus::{EventBus, Postbox}; @@ -8,10 +11,13 @@ use esp_idf_svc::eventloop::{ EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User, }; use esp_idf_sys::c_types; -use log::{warn}; +use log::warn; use serde_json::json; -use crate::voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; +use crate::{ + time::Time, + voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, +}; pub static mut CHARGE_STATE_EVENT_LOOP: Option< EspEventLoop>, @@ -27,6 +33,7 @@ pub enum ChargeStatus { pub struct ChargeControllerState { pub status: ChargeStatus, pub pin_state: PinState, + pub charge_deadline_at: Duration, } impl ChargeControllerState { @@ -34,6 +41,7 @@ impl ChargeControllerState { Self { status: ChargeStatus::Charging, pin_state: PinState::Low, + charge_deadline_at: Duration::ZERO, } } @@ -46,7 +54,13 @@ impl ChargeControllerState { PinState::Low => "Low", PinState::High => "High", }; - json!({ "status": status, "pin_state": pin_state}).to_string() + 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() } } @@ -112,7 +126,14 @@ impl ChargeController { if obj.battery_voltage < 12600 { state.status = ChargeStatus::Charging; } else { - state.status = ChargeStatus::Charged; + 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 => {