Merge pull request 'v0.2:重写项目。' (#6) from v0.2 into master

Reviewed-on: Ivan/ups-esp32c3-rust#6
This commit is contained in:
Ivan Li 2022-09-12 11:58:32 +08:00
commit 16d1e1962f
24 changed files with 1482 additions and 638 deletions

View File

@ -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" }

45
.devcontainer/Dockerfile Normal file
View File

@ -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" ]

View File

@ -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"
}

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
target

34
.gitpod.Dockerfile vendored Normal file
View File

@ -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

23
.gitpod.yml Normal file
View File

@ -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

View File

@ -1,9 +1,9 @@
[package]
authors = ["Ivan Li <ivanli2048@gmail.com>"]
edition = "2018"
name = "ups-esp32c3-rust"
version = "0.2.0"
authors = ["Ivan Li <ivanli2048@gmail.com>"]
edition = "2021"
resolver = "2"
version = "0.1.0"
[profile.release]
opt-level = "s"
@ -17,18 +17,20 @@ pio = ["esp-idf-sys/pio"]
[dependencies]
anyhow = "1"
embedded-graphics = "0.7.1"
embedded-hal = "1.0.0-alpha.8"
embedded-hal-0-2 = {package = "embedded-hal", version = "0.2.7", features = ["unproven"]}
embedded-hal = "0.2.7"
embedded-svc = "0.22.0"
env_logger = "0.9.0"
esp-idf-hal = "0.38.0"
esp-idf-svc = "0.42.1"
esp-idf-sys = { version = "0.31.6", features = ["binstart"] }
log = "0.4.17"
retry = "1.3.1"
ssd1306 = "0.7.0"
serde = {version="1.0.144", features = ["derive"]}
serde_json = "1.0.83"
[build-dependencies]
anyhow = "1.0.57"
embuild = "0.29.1"
embuild = "0.29"
anyhow = "1"
[patch.crates-io]
esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal", branch = "master" }

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# UPS ESP32-C3 Rust
一个使用 Rust 语言开发的 UPS 程序,适用于 乐鑫×安信可的 ESP32-C3-32S 模块。
## Features
- [x] 输入电压、输出电压、电池电压检测;
- [x] Wi-Fi 联网;
- [x] 以 NTP 方式校准时间;
- [x] 以 MQTT 方式上报状态;
- [x] 提供 UPS 电源输出的控制信号;
- [ ] 提供 UPS 内置电池充电电路电源输入的控制信号;
## GPIO 定义
- `GPIO 1`UPS 输入电压检测,使用 `ADC 1`
- `GPIO 2`:电池电芯电压检测,使用 `ADC 1`
- `GPIO 3`UPS 输出电压检测,使用 `ADC 1`
- `GPIO 4`:蜂鸣器模拟信号输出,使用 `CHANNEL 0`, `TIMER 0`
- `GPIO 5`:工作状态指示灯信号输出;
- `GPIO 6`UPS 输出控制信号,适用于 P-MOS 开关;

116
docs/README.md Normal file
View File

@ -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

24
scripts/build.sh Executable file
View File

@ -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

22
scripts/flash.sh Executable file
View File

@ -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

36
scripts/run-wokwi.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -1,78 +1,124 @@
use esp_idf_hal::gpio::OutputPin;
use esp_idf_hal::ledc;
use embedded_svc::timer::{PeriodicTimer, TimerService};
use esp_idf_hal::gpio::{Gpio4, Output};
use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer};
use esp_idf_hal::ledc::{CHANNEL0, TIMER0};
use esp_idf_hal::prelude::*;
use esp_idf_sys::EspError;
use std::thread;
use esp_idf_svc::timer::{EspTimer, EspTimerService};
use esp_idf_sys::{ledc_mode_t_LEDC_LOW_SPEED_MODE, ledc_set_freq, EspError};
use log::{info, warn};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
type LedcChannel<P, T, C> = Channel<C, T, Timer<T>, P>;
pub struct Beep<P: OutputPin, T: ledc::HwTimer, C: ledc::HwChannel> {
#[derive(Clone, Copy, Debug)]
struct BeepState {
beat: u8,
ringtone: ringtone::Type,
channel: LedcChannel<P, T, C>,
duty: u32,
}
impl<P: OutputPin, T: ledc::HwTimer, C: ledc::HwChannel> Beep<P, T, C> {
pub fn new(pin: P, timer: T, channel: C) -> Result<Self, EspError> {
let channel = Self::init_channel(pin, timer, channel)?;
let max_duty = channel.get_max_duty();
return Ok(Beep {
channel,
impl BeepState {
pub fn new() -> Self {
Self {
beat: 0,
duty: max_duty * 3 / 4,
ringtone: ringtone::SILENCE,
}
}
pub fn from_ringtone(ringtone: ringtone::Type) -> Self {
Self { beat: 0, ringtone }
}
}
type BeepChannel = Channel<CHANNEL0, TIMER0, Timer<TIMER0>, Gpio4<Output>>;
pub struct Beep {
watch_timer: Option<EspTimer>,
state: BeepState,
channel: Arc<Mutex<BeepChannel>>,
}
impl Beep {
pub fn new() -> anyhow::Result<Self> {
let pin = unsafe { Gpio4::<Output>::new() }
.into_output()
.map_err(|err| anyhow::anyhow!("Failed to set GPIO 4 as ledc output. {}", err))?;
let hw_timer = unsafe { TIMER0::new() };
let hw_channel = unsafe { CHANNEL0::new() };
let channel = Self::init_channel(pin, hw_timer, hw_channel, 1000.Hz().into())
.map_err(|err| anyhow::anyhow!("Failed to initialize channel. {}", err))?;
return anyhow::Ok(Beep {
watch_timer: None,
state: BeepState::new(),
channel: Arc::new(Mutex::new(channel)),
});
}
fn init_channel(pin: P, timer: T, channel: C) -> Result<LedcChannel<P, T, C>, EspError> {
let config = TimerConfig::default().frequency(2.kHz().into());
fn init_channel(
pin: Gpio4<Output>,
timer: TIMER0,
channel: CHANNEL0,
frequency: Hertz,
) -> Result<LedcChannel<Gpio4<Output>, TIMER0, CHANNEL0>, EspError> {
let config = TimerConfig::default().frequency(frequency);
let timer = Timer::new(timer, &config)?;
let channel: Channel<C, T, Timer<T>, P> = Channel::new(channel, timer, pin)?;
let channel = Channel::new(channel, timer, pin)?;
return Ok(channel);
}
pub fn play(&mut self, rx: &mut std::sync::mpsc::Receiver<ringtone::Type>) {
loop {
let curr_ringtone = rx.try_recv().unwrap_or_else(|_| self.ringtone);
if !curr_ringtone.eq(&mut self.ringtone) {
self.beat = 0;
self.ringtone = curr_ringtone;
}
let curr = curr_ringtone[self.beat as usize];
if curr {
self.channel.set_duty(self.duty).expect("Failed to set duty");
} else {
self.channel.set_duty(0).expect("Failed to set duty");
}
thread::sleep(Duration::from_millis(100));
self.beat += 1;
if self.beat == 16 {
self.beat = 0;
}
pub fn play(&mut self, ringtone: ringtone::Type) -> anyhow::Result<()> {
if self.state.ringtone != ringtone {
self.state = BeepState::from_ringtone(ringtone);
info!("change: {:?}", ringtone);
} else {
return Ok(());
}
let state = Arc::new(Mutex::new(self.state));
let channel = self.channel.to_owned();
let mut timer = EspTimerService::new()?
.timer(move || match state.lock().as_mut() {
Ok(state) => {
if let Err(err) = Self::play_once(state, channel.lock().unwrap()) {
warn!("{}", err);
}
}
Err(err) => {
warn!("Failed to lock state. {}", err);
}
})
.map_err(|err| anyhow::anyhow!("Init Timer Failed. {}", err))?;
timer.every(Duration::from_millis(250))?;
self.watch_timer = Some(timer);
return anyhow::Ok(());
}
fn play_once(
state: &mut BeepState,
mut channel: MutexGuard<BeepChannel>,
) -> anyhow::Result<()> {
if state.ringtone.len() <= state.beat as usize {
state.beat = 0;
}
let curr = state.ringtone[state.beat as usize];
if curr == 0 {
channel.set_duty(0).expect("Failed to set duty");
} else {
unsafe { ledc_set_freq(ledc_mode_t_LEDC_LOW_SPEED_MODE, 0, curr); }
channel.set_duty(60).expect("Failed to set duty");
}
state.beat += 1;
if state.beat == 16 {
state.beat = 0;
}
return anyhow::Ok(());
}
}
pub mod ringtone {
pub type Type = [bool; 16];
pub const ADAPTER_DOWN: Type = [
true, true, true, true, false, false, false, false, false, false, false, false, false,
false, false, false,
];
pub type Type = [u32; 16];
pub const ADAPTER_DOWN: Type = [2300, 2000, 2250, 1950, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
pub const BATTERY_LOW: Type = [
true, true, false, false, true, true, false, false, true, true, false, false, true, true,
false, false,
2000, 1950, 0, 0, 1980, 1900, 0, 0, 2000, 1900, 0, 0, 1980, 1900, 0, 0,
];
pub const SILENCE: Type = [false; 16];
pub const SILENCE: Type = [0; 16];
pub const SHUTDOWN: Type = [
true, false, true, true, true, true, false, true, true, true, true, true, false, false,
false, false,
3450, 3500, 0, 3500, 3050, 3000, 0, 3000, 3050, 1000, 1000, 1000, 0, 0, 0, 0,
];
}

View File

@ -1,4 +1,4 @@
use embedded_hal::digital::blocking::OutputPin;
use embedded_hal::digital::v2::OutputPin;
use std::thread;
use std::time::Duration;
@ -32,16 +32,16 @@ where
}
}
pub fn play(&mut self) {
pub fn play(&mut self) where <T as embedded_hal::digital::v2::OutputPin>::Error: std::fmt::Debug {
loop {
if self.stopped {
break;
}
self.toggle().unwrap();
thread::sleep(Duration::from_millis(50));
thread::sleep(Duration::from_millis(10));
self.toggle().unwrap();
thread::sleep(Duration::from_millis(950));
thread::sleep(Duration::from_millis(990));
}
}
}

187
src/charge_controller.rs Normal file
View File

@ -0,0 +1,187 @@
use std::{
sync::{Arc, Mutex, MutexGuard},
time::Duration,
};
use embedded_hal::digital::v2::{OutputPin, PinState};
use embedded_svc::event_bus::{EventBus, Postbox};
use esp_idf_hal::gpio::{Gpio7, Output};
use esp_idf_svc::eventloop::{
Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData,
EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User,
};
use esp_idf_sys::c_types;
use log::warn;
use serde_json::json;
use crate::{
time::Time,
voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP},
};
pub static mut CHARGE_STATE_EVENT_LOOP: Option<
EspEventLoop<esp_idf_svc::eventloop::User<Background>>,
> = None;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ChargeStatus {
Charging,
Charged,
}
#[derive(Debug, Clone, Copy)]
pub struct ChargeControllerState {
pub status: ChargeStatus,
pub pin_state: PinState,
pub charge_deadline_at: Duration,
}
impl ChargeControllerState {
pub fn new() -> Self {
Self {
status: ChargeStatus::Charging,
pin_state: PinState::Low,
charge_deadline_at: Duration::ZERO,
}
}
pub fn to_json(&self) -> String {
let status = match self.status {
ChargeStatus::Charging => "Charging",
ChargeStatus::Charged => "Charged",
};
let pin_state = match self.pin_state {
PinState::Low => "Low",
PinState::High => "High",
};
let now = Time::new().get_time();
let charging_count_down = if now > self.charge_deadline_at {
-1i64
} else {
(self.charge_deadline_at - now).as_secs() as i64
};
json!({ "status": status, "pin_state": pin_state, "charging_count_down": charging_count_down}).to_string()
}
}
impl EspTypedEventSource for ChargeControllerState {
fn source() -> *const c_types::c_char {
b"Charge\0".as_ptr() as *const _
}
}
impl EspTypedEventSerializer<ChargeControllerState> for ChargeControllerState {
fn serialize<R>(
event: &ChargeControllerState,
f: impl for<'a> FnOnce(&'a EspEventPostData) -> R,
) -> R {
f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) })
}
}
impl EspTypedEventDeserializer<ChargeControllerState> for ChargeControllerState {
fn deserialize<R>(
data: &EspEventFetchData,
f: &mut impl for<'a> FnMut(&'a ChargeControllerState) -> R,
) -> R {
f(unsafe { data.as_payload() })
}
}
type ChargeCtlPin = Gpio7<Output>;
pub struct ChargeController {
pub state: ChargeControllerState,
voltage_subscription: Option<EspSubscription<User<Background>>>,
pin: Arc<Mutex<ChargeCtlPin>>,
}
impl ChargeController {
pub fn new() -> anyhow::Result<Self> {
let pin = unsafe { Gpio7::<Output>::new() }
.into_output()
.map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?;
match EspBackgroundEventLoop::new(&Default::default()) {
Ok(eventloop) => unsafe { CHARGE_STATE_EVENT_LOOP = Some(eventloop) },
Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err),
}
anyhow::Ok(Self {
state: ChargeControllerState::new(),
voltage_subscription: None,
pin: Arc::new(Mutex::new(pin)),
})
}
pub fn watch(&mut self) -> anyhow::Result<()> {
let mut state = self.state.to_owned();
let pin = self.pin.to_owned();
if let Some(event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } {
let voltage_subscription = event_loop
.subscribe(move |obj: &VoltageDetectionWorker| {
match state.status {
ChargeStatus::Charging => {
if obj.battery_voltage < 12600 {
state.status = ChargeStatus::Charging;
} else {
let now = Time::new().get_time();
if state.charge_deadline_at == Duration::ZERO {
state.charge_deadline_at = now + Duration::from_secs(600);
} else if now > state.charge_deadline_at {
state.status = ChargeStatus::Charged;
} else {
state.status = ChargeStatus::Charging;
}
}
}
ChargeStatus::Charged => {
if obj.battery_voltage < 10500 {
state.status = ChargeStatus::Charging;
} else {
state.status = ChargeStatus::Charged;
}
}
}
match pin.lock() {
Ok(pin) => {
if let Err(err) = Self::output_ctl(&mut state, pin) {
warn!("Put Control Pin State Failed. {}", err);
}
}
Err(_) => todo!(),
}
if let Some(event_loop) = unsafe { CHARGE_STATE_EVENT_LOOP.as_mut() } {
if let Err(err) = event_loop.post(&state, None) {
warn!("Post DC Out Status Failed. {}", err);
}
} else {
warn!("CHARGE_STATE_EVENT_LOOP is None");
}
})
.map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?;
self.voltage_subscription = Some(voltage_subscription);
} else {
anyhow::bail!("Voltage Event Loop is None");
}
anyhow::Ok(())
}
fn output_ctl(
state: &mut ChargeControllerState,
mut pin: MutexGuard<ChargeCtlPin>,
) -> anyhow::Result<()> {
if ChargeStatus::Charging == state.status {
pin.set_high()
.map_err(|err| anyhow::anyhow!("Set DC Output Control Pin High Failed. {}", err))?;
state.pin_state = PinState::High;
} else if ChargeStatus::Charged == state.status {
pin.set_low()
.map_err(|err| anyhow::anyhow!("Set DC Output Control Pin Low Failed. {}", err))?;
state.pin_state = PinState::Low;
}
return anyhow::Ok(());
}
}

View File

@ -1,181 +1,251 @@
use anyhow::{Ok, Result, anyhow};
use retry;
use std::{
sync::{
mpsc,
Arc, Mutex,
},
thread,
time::{self, SystemTime},
sync::{Arc, Mutex, MutexGuard},
time::Duration,
};
use embedded_hal::digital::blocking::OutputPin;
use log::*;
use embedded_hal::digital::v2::{OutputPin, PinState};
use embedded_svc::event_bus::{EventBus, Postbox};
use esp_idf_hal::gpio::{Gpio6, Output};
use esp_idf_svc::eventloop::{
Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData,
EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User,
};
use esp_idf_sys::c_types;
use log::warn;
use serde_json::json;
use crate::{
time::Time,
voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP},
};
const WAITING_OFF_DURATION: u64 = 60;
const WAITING_ON_DURATION: u64 = 60;
pub static mut DC_OUT_STATE_EVENT_LOOP: Option<
EspEventLoop<esp_idf_svc::eventloop::User<Background>>,
> = None;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DcOutStatus {
WaitingOn(Duration),
On,
Off,
TurningOn(mpsc::Receiver<bool>),
TurningOff(mpsc::Receiver<bool>),
WaitingOff,
TurningOff(Duration),
}
impl DcOutStatus {
pub fn is_on(&self) -> bool {
match self {
DcOutStatus::On => true,
_ => false,
}
}
pub fn is_off(&self) -> bool {
match self {
DcOutStatus::Off => true,
_ => false,
}
}
#[derive(Debug, Clone, Copy)]
pub struct DcOutControllerState {
pub status: DcOutStatus,
pub pin_state: PinState,
}
pub struct DcOutController<P>
where
P: OutputPin + Send,
{
pin: Arc<Mutex<P>>,
shutdown_tx: Option<mpsc::Sender<bool>>,
status: DcOutStatus,
}
impl<P> DcOutController<P>
where
P: OutputPin + Send,
P: 'static,
P: std::marker::Sync,
{
pub fn new(pin: P) -> Self {
return Self {
impl DcOutControllerState {
pub fn new() -> Self {
Self {
status: DcOutStatus::On,
pin: Arc::new(Mutex::new(pin)),
shutdown_tx: None,
pin_state: PinState::Low,
}
}
fn handle_adapter_down(&mut self) {
match self.status {
DcOutStatus::On => {
self.status = DcOutStatus::WaitingOff;
}
DcOutStatus::WaitingOn(_) => {
self.status = DcOutStatus::Off;
}
DcOutStatus::TurningOff(target) => {
let now = Time::new().get_time();
if now > target {
self.status = DcOutStatus::Off;
}
}
_ => {}
};
}
pub fn open(&mut self) -> Result<()> {
if self.status.is_on() {
trace!("DC OUT already on, skipping");
return Ok(());
}
let pin = self.pin.clone();
let mut pin = retry::retry(retry::delay::Fixed::from_millis(100).take(10), || {
pin.lock()
})
.map_err(|_| anyhow::anyhow!("Failed to lock pin"))?;
retry::retry(retry::delay::Fixed::from_millis(100).take(10), || {
pin.set_low()
})
.map_err(|_| anyhow::anyhow!("Failed to lock pin"))?;
self.status = DcOutStatus::On;
info!("DC OUT ON");
Ok(())
}
pub fn shutdown(&mut self) {
if self.shutdown_tx.is_some() {
trace!("Shutdown task already running, skipping...");
return;
}
info!("Shutdown task started...");
let (tx, rx) = mpsc::channel();
self.shutdown_tx = Some(tx);
let pin = Arc::clone(&self.pin);
let (status_tx, status_rx) = mpsc::channel();
self.status = DcOutStatus::TurningOff(status_rx);
thread::spawn(move || {
// Wait for computer shutdown finished
let target_at = SystemTime::now() + time::Duration::from_secs(10);
loop {
if rx.try_recv().is_ok_and(|state| !*state) {
break;
}
if target_at > SystemTime::now() {
thread::sleep(time::Duration::from_millis(1000));
continue;
}
info!("Shutdown timeout, force shutdown...");
let result = || {
retry::retry(retry::delay::Fixed::from_millis(1000).take(10), || {
return pin
.lock()
.map_err(|_| anyhow::anyhow!("Can not lock pin"))?
.set_high()
.map_err(|_| anyhow::anyhow!("Can not shutdown"));
}).map_err(|_| anyhow!("Failed to send shutdown status"))?;
status_tx.send(true).map_err(|_| anyhow!("Failed to send shutdown status"))?;
Ok(())
};
if let Err(e) = result() {
warn!("Failed to shutdown: {}", e);
} else {
info!("Shutdown finished");
}
break;
fn turn_off(&mut self) {
let now = Time::new().get_time();
match self.status {
DcOutStatus::On => {
self.status =
DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION));
}
});
}
pub fn stop_shutdown(&mut self) -> Result<()> {
if let Some(tx) = self.shutdown_tx.as_mut() {
info!("Shutdown task stopped...");
if retry::retry(retry::delay::Fixed::from_millis(100).take(5), || {
tx.send(false)
})
.map_err(|_| anyhow::anyhow!("Failed to stop shutdown"))
.is_err() {
warn!("Message to shutdown task was not sent");
} else {
info!("Shutdown task stopped");
DcOutStatus::WaitingOff => {
self.status =
DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION));
}
self.shutdown_tx = None;
DcOutStatus::WaitingOn(_) => {
self.status = DcOutStatus::Off;
}
DcOutStatus::TurningOff(target) => {
if target < now {
self.status = DcOutStatus::Off;
}
}
_ => {}
};
Ok(())
}
pub fn get_status(&mut self) -> DcOutStatus {
match &self.status {
DcOutStatus::TurningOn(rx) => {
rx.try_recv().map_or((), |state| {
trace!("DcOutStatus::TurningOn({})", state);
if state {
self.status = DcOutStatus::On;
} else {
self.status = DcOutStatus::Off;
}
})
},
DcOutStatus::TurningOff(rx) => {
rx.try_recv().map_or((), |state| {
trace!("DcOutStatus::TurningOff({})", state);
if state {
self.status = DcOutStatus::Off;
} else {
self.status = DcOutStatus::On;
}
})
},
_default => {}
}
match &self.status {
DcOutStatus::On => DcOutStatus::On,
DcOutStatus::Off => DcOutStatus::Off,
_ => DcOutStatus::On,
fn turn_on(&mut self) {
let now = Time::new().get_time();
match self.status {
DcOutStatus::WaitingOff => {
self.status = DcOutStatus::On;
}
DcOutStatus::Off => {
self.status =
DcOutStatus::WaitingOn(now + Duration::from_secs(WAITING_ON_DURATION));
}
DcOutStatus::WaitingOn(target) => {
if target <= now {
self.status = DcOutStatus::On;
}
}
DcOutStatus::TurningOff(target) => {
if target <= now {
self.status = DcOutStatus::On;
}
}
_ => {}
};
}
pub fn to_json(&self) -> String {
let status = match self.status {
DcOutStatus::WaitingOn(_) => "WaitingOn",
DcOutStatus::On => "On",
DcOutStatus::Off => "Off",
DcOutStatus::WaitingOff => "WaitingOff",
DcOutStatus::TurningOff(_) => "TurningOff",
};
let pin_state = match self.pin_state {
PinState::Low => "Low",
PinState::High => "High",
};
let now = Time::new().get_time();
let target = match self.status {
DcOutStatus::WaitingOn(target) => target,
DcOutStatus::TurningOff(target) => target,
_ => Duration::ZERO,
};
let seconds = if now < target {
target - now
} else {
Duration::ZERO
}
.as_secs();
json!({ "status": status, "pin_state": pin_state, "seconds": seconds }).to_string()
}
}
impl EspTypedEventSource for DcOutControllerState {
fn source() -> *const c_types::c_char {
b"DcOut\0".as_ptr() as *const _
}
}
impl EspTypedEventSerializer<DcOutControllerState> for DcOutControllerState {
fn serialize<R>(
event: &DcOutControllerState,
f: impl for<'a> FnOnce(&'a EspEventPostData) -> R,
) -> R {
f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) })
}
}
impl EspTypedEventDeserializer<DcOutControllerState> for DcOutControllerState {
fn deserialize<R>(
data: &EspEventFetchData,
f: &mut impl for<'a> FnMut(&'a DcOutControllerState) -> R,
) -> R {
f(unsafe { data.as_payload() })
}
}
type DcOutPin = Gpio6<Output>;
pub struct DcOutController {
pub state: DcOutControllerState,
voltage_subscription: Option<EspSubscription<User<Background>>>,
pin: Arc<Mutex<DcOutPin>>,
}
impl DcOutController {
pub fn new() -> anyhow::Result<Self> {
let pin = unsafe { Gpio6::<Output>::new() }
.into_output()
.map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?;
match EspBackgroundEventLoop::new(&Default::default()) {
Ok(eventloop) => unsafe { DC_OUT_STATE_EVENT_LOOP = Some(eventloop) },
Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err),
}
anyhow::Ok(Self {
state: DcOutControllerState::new(),
voltage_subscription: None,
pin: Arc::new(Mutex::new(pin)),
})
}
pub fn watch(&mut self) -> anyhow::Result<()> {
let mut state = self.state.to_owned();
let pin = self.pin.to_owned();
if let Some(event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } {
let voltage_subscription = event_loop
.subscribe(move |obj: &VoltageDetectionWorker| {
if obj.adapter_voltage < 1000 {
if obj.battery_voltage < 1000 {
state.turn_off();
} else {
state.handle_adapter_down();
}
} else {
state.turn_on();
}
match pin.lock() {
Ok(pin) => {
if let Err(err) = Self::output_ctl(&mut state, pin) {
warn!("Put Control Pin State Failed. {}", err);
}
}
Err(_) => todo!(),
}
if let Some(event_loop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } {
if let Err(err) = event_loop.post(&state, None) {
warn!("Post DC Out Status Failed. {}", err);
}
} else {
warn!("DC_OUT_STATE_EVENT_LOOP is None");
}
})
.map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?;
self.voltage_subscription = Some(voltage_subscription);
} else {
anyhow::bail!("Voltage Event Loop is None");
}
anyhow::Ok(())
}
fn output_ctl(
state: &mut DcOutControllerState,
mut pin: MutexGuard<DcOutPin>,
) -> anyhow::Result<()> {
if DcOutStatus::Off == state.status {
pin.set_high()
.map_err(|err| anyhow::anyhow!("Set DC Output Control Pin High Failed. {}", err))?;
state.pin_state = PinState::High;
} else if DcOutStatus::On == state.status {
pin.set_low()
.map_err(|err| anyhow::anyhow!("Set DC Output Control Pin Low Failed. {}", err))?;
state.pin_state = PinState::Low;
}
return anyhow::Ok(());
}
}

View File

@ -1,89 +1,188 @@
#![feature(is_some_with)]
use esp_idf_sys as _;
use log::{error, info};
use std::{thread, time::Duration, sync::mpsc, env};
use crate::wifi::WiFi;
use embedded_svc::event_bus::{EventBus};
use esp_idf_svc::eventloop::{EspBackgroundEventLoop};
use esp_idf_sys::{self as _};
use log::*;
use std::{
env,
thread::{self, sleep},
time::Duration,
};
mod beep;
mod blink;
mod dc_out_controller;
mod manager;
mod screen;
mod message_queue;
mod time;
mod voltage_detection;
mod wifi;
mod charge_controller;
use crate::{
beep::{ringtone, Beep},
dc_out_controller::{DcOutController, DcOutControllerState, DC_OUT_STATE_EVENT_LOOP},
message_queue::MqDto,
voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, charge_controller::{CHARGE_STATE_EVENT_LOOP, ChargeControllerState, ChargeController},
};
use crate::{
message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet,
};
fn main() {
env::set_var("DEFMT_LOG", "trace");
env::set_var("RUST_BACKTRACE", "1");
env::set_var("RUST_LOG", "trace");
env_logger::init();
// Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
// or else some patches to the runtime implemented by esp-idf-sys might not link properly.
esp_idf_sys::link_patches();
info!("Hello, world!");
let peripherals = esp_idf_hal::peripherals::Peripherals::take().unwrap();
let blink_pin = peripherals.pins.gpio5;
let beep_pin = peripherals.pins.gpio6;
let ledc_timer0 = peripherals.ledc.timer0;
let ledc_channel0 = peripherals.ledc.channel0;
let dc_out_ctl_pin = peripherals.pins.gpio3;
let i2c0 = peripherals.i2c0;
let sda_pin = peripherals.pins.gpio4;
let scl_pin = peripherals.pins.gpio10;
let adc1 = peripherals.adc1;
let adapter_pin = peripherals.pins.gpio1;
let battery_pin = peripherals.pins.gpio2;
let (tx, mut rx) = mpsc::channel();
info!("Starting");
match EspBackgroundEventLoop::new(&Default::default()) {
Ok(eventloop) => unsafe { VOLTAGE_EVENTLOOP = Some(eventloop) },
Err(err) => error!("Init Event Loop failed. {:?}", err),
};
thread::spawn(move || {
let mut blink =
blink::Blink::new(blink_pin.into_output().expect("Failed to set GPIO5 as output"));
let mut blink = blink::Blink::new(
blink_pin
.into_output()
.expect("Failed to set GPIO5 as output"),
);
blink.play();
});
thread::spawn(move || {
thread::sleep(Duration::from_millis(5000));
});
let voltage_detection = VoltageDetection::new();
thread::spawn(move || {
let mut beep = beep::Beep::new(
beep_pin.into_output().expect("Failed to set GPIO6 as output"),
ledc_timer0,
ledc_channel0,
)
.expect("Failed to create beep");
beep.play(&mut rx);
});
voltage_detection.unwrap()
.watching()
.expect("Can not watch voltages.");
let display = screen::Screen::new(i2c0, sda_pin, scl_pin).expect("Failed to create screen");
let _wifi = Internet::new().unwrap();
let dc_out_ctl = dc_out_controller::DcOutController::new(
dc_out_ctl_pin
.into_output()
.expect("Failed to set GPIO3 as output"),
);
let mut manager = manager::Manager::new(
dc_out_ctl,
display,
adc1,
adapter_pin.into_analog_atten_11db().expect("Failed to set GPIO1 as analog input"),
battery_pin.into_analog_atten_11db().expect("Failed to set GPIO2 as analog input"),
tx,
).expect("Failed to create manager");
let mut time = Time::new();
time.sync().unwrap();
let wifi = WiFi::new().expect("Failed to connect wifi");
sleep(Duration::from_millis(100));
let mut beep = Beep::new().unwrap();
let mut dc_out_controller =
DcOutController::new().expect("Can not get DcOutController instance");
dc_out_controller
.watch()
.expect("Can not watch for dc_out_controller");
let mut charge_controller = ChargeController::new().expect("Can not get ChargeController instance");
charge_controller
.watch()
.expect("Can not watch for charge_controller");
sleep(Duration::from_millis(100));
let mut _mq = MessageQueue::new();
let _mq_subscription;
match _mq.watch() {
Err(err) => {
error!("Can not watch MessageQueue. {}", err);
}
Ok(subscription) => _mq_subscription = subscription,
}
let _mq_tx_for_voltage = _mq.tx.clone();
let _mq_tx_for_dc_out_state = _mq.tx.clone();
let _mq_tx_for_charge_state = _mq.tx.clone();
let _voltage_subscription;
if let Some(voltage_event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } {
_voltage_subscription = voltage_event_loop
.subscribe(move |message: &VoltageDetectionWorker| {
if let Ok(json_str) = serde_json::to_string(&message) {
match _mq_tx_for_voltage.lock() {
Ok(tx) => {
let result = tx.send(MqDto {
topic: "voltage".to_string(),
message: json_str,
});
if let Err(err) = result {
warn!("send voltage to mq message failed. {}", err)
}
}
Err(err) => warn!("send voltage to mq message failed. {}", err),
}
}
})
.expect(" Listening Event Loop Failed");
} else {
panic!("VOLTAGE_EVENTLOOP is undefined!");
}
let _dc_out_state_subscription;
if let Some(dc_state_event_loop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } {
_dc_out_state_subscription = dc_state_event_loop
.subscribe(move |message: &DcOutControllerState| {
match message.status {
dc_out_controller::DcOutStatus::WaitingOff => {
beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep.")
}
dc_out_controller::DcOutStatus::WaitingOn(_) => {
beep.play(ringtone::BATTERY_LOW).expect("Can not beep.")
}
dc_out_controller::DcOutStatus::TurningOff(_) => {
beep.play(ringtone::SHUTDOWN).expect("Can not beep.")
}
_ => beep.play(ringtone::SILENCE).expect("Can not beep."),
}
match _mq_tx_for_dc_out_state.lock() {
Ok(tx) => {
let result = tx.send(MqDto {
topic: "dc_out_state".to_string(),
message: message.to_json(),
});
if let Err(err) = result {
warn!("send dc_out_state message failed. {}", err)
}
}
Err(err) => warn!("send dc_out_state to mq message failed. {}", err),
}
})
.expect(" Listening Event Loop Failed");
} else {
panic!("DC_OUT_STATE_EVENT_LOOP is undefined!");
}
let _charge_state_subscription;
if let Some(charge_state_event_loop) = unsafe { CHARGE_STATE_EVENT_LOOP.as_mut() } {
_charge_state_subscription = charge_state_event_loop
.subscribe(move |message: &ChargeControllerState| {
match _mq_tx_for_charge_state.lock() {
Ok(tx) => {
let result = tx.send(MqDto {
topic: "charge_state".to_string(),
message: message.to_json(),
});
if let Err(err) = result {
warn!("send charge_state message failed. {}", err)
}
}
Err(err) => warn!("send charge_state to mq message failed. {}", err),
}
})
.expect(" Listening Event Loop Failed");
} else {
panic!("CHARGE_STATE_EVENT_LOOP is undefined!");
}
loop {
match manager.handling_once() {
Ok(_) => {}
Err(err) => {
error!("Exec manager tick task failed: {}", err);
}
}
thread::sleep(Duration::from_millis(100));
sleep(Duration::from_millis(100));
}
}

View File

@ -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<Atten11dB<ADC1>>;
type BatteryGpio = Gpio2<Atten11dB<ADC1>>;
pub struct Manager<C>
where
C: OutputPin + Send,
{
dc_out_controller: DcOutController<C>,
screen: Screen,
adc: PoweredAdc<ADC1>,
adapter_pin: AdapterGpio,
battery_pin: BatteryGpio,
tx: mpsc::Sender<ringtone::Type>,
adapter_downed_at: Option<SystemTime>,
}
impl<C> Manager<C>
where
C: OutputPin + Send,
C: 'static,
C: std::marker::Sync,
{
pub fn new(
dc_out_controller: DcOutController<C>,
screen: Screen,
adc1: ADC1,
adapter_pin: AdapterGpio,
battery_pin: BatteryGpio,
tx: mpsc::Sender<ringtone::Type>,
) -> Result<Self> {
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<f32> {
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<f32> {
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
}

113
src/message_queue.rs Normal file
View File

@ -0,0 +1,113 @@
use std::{
sync::{
mpsc::{self, Receiver, Sender},
Arc, Mutex,
},
thread::{self, spawn},
time::Duration,
};
use anyhow::Result;
use embedded_svc::mqtt::client::{utils::ConnState, Client, Connection, MessageImpl, Publish, QoS};
use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
use esp_idf_sys::EspError;
use log::*;
pub struct MessageQueue {
pub tx: Arc<Mutex<Sender<MqDto>>>,
pub rx: Arc<Mutex<Receiver<MqDto>>>,
}
pub struct MessageQueueWatcher {
pub client: EspMqttClient<ConnState<MessageImpl, EspError>>,
}
impl MessageQueueWatcher {
pub fn new() -> Result<Self> {
let conf = MqttClientConfiguration {
client_id: Some("rust-esp32-std-demo"),
crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach),
..Default::default()
};
let (mut client, mut connection) =
EspMqttClient::new_with_conn("mqtt://192.168.31.11:1883", &conf)?;
info!("MQTT client started");
thread::spawn(move || {
info!("MQTT Listening for messages");
while let Some(msg) = connection.next() {
match msg {
Err(e) => info!("MQTT Message ERROR: {}", e),
Ok(msg) => info!("MQTT Message: {:?}", msg),
}
}
info!("MQTT connection loop exit");
});
client
.subscribe("esp32-c3-rust-wifi-demo", QoS::AtMostOnce)
.map_err(|err| anyhow::anyhow!("subscribe message from queue failed. {}", err))?;
info!("Subscribed to all topics (esp32-c3-rust-wifi-demo)");
client
.publish(
"esp32-c3-rust-wifi-demo/ping",
QoS::AtMostOnce,
false,
"Hello".as_bytes(),
)
.map_err(|err| anyhow::anyhow!("publish message to queue failed. {}", err))?;
info!("Published a hello message to topic \"esp32-c3-rust-wifi-demo/ping\"");
anyhow::Ok(Self { client })
}
pub fn publish(&mut self, topic: &str, bytes: &[u8]) -> Result<u32> {
self.client
.publish(
format!("{}/{}", "ups_0_2", topic).as_str(),
QoS::AtMostOnce,
false,
bytes,
)
.map_err(|err| anyhow::anyhow!("publish message to queue was failed!. {}", err))
}
}
impl MessageQueue {
pub fn new() -> Self {
let (tx, rx) = mpsc::channel();
return MessageQueue {
tx: Arc::new(Mutex::new(tx)),
rx: Arc::new(Mutex::new(rx)),
};
}
pub fn watch(&mut self) -> anyhow::Result<()> {
let mut watcher = MessageQueueWatcher::new()
.map_err(|err| anyhow::anyhow!("Create MessageQueueWatcher failed. {}", err))?;
let rx = self.rx.to_owned();
// let (tx, rx) = mpsc::channel::<MqDto>();
spawn(move || loop {
if let Ok(dto) = rx.lock().unwrap().recv_timeout(Duration::from_millis(400)) {
if let Err(err) = watcher.publish(dto.topic.as_str(), dto.message.as_bytes()) {
warn!("Can not publish message to MQTT. {}", err);
}
}
thread::sleep(Duration::from_millis(100))
});
anyhow::Ok(())
}
}
#[derive(Debug, Clone)]
pub struct MqDto {
pub message: String,
pub topic: String,
}

View File

@ -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<Master<I2C0, gpio::Gpio4<gpio::Unknown>, gpio::Gpio10<gpio::Unknown>>>,
DisplaySize128x64,
BufferedGraphicsMode<DisplaySize128x64>,
>;
pub struct Screen {
pub display: Option<Display>,
}
impl Screen {
pub fn new(
i2c: i2c::I2C0,
sda: gpio::Gpio4<gpio::Unknown>,
scl: gpio::Gpio10<gpio::Unknown>,
) -> Result<Self> {
let config = <i2c::config::MasterConfig as Default>::default().baudrate(400.kHz().into());
let mut i2c = i2c::Master::<i2c::I2C0, _, _>::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(())
}
}

33
src/time.rs Normal file
View File

@ -0,0 +1,33 @@
use embedded_svc::sys_time::SystemTime;
use esp_idf_svc::sntp::{self, EspSntp};
use esp_idf_svc::systime::EspSystemTime;
use log::{info};
use std::time::{Duration};
pub struct Time {
sntp: Option<Box<EspSntp>>,
}
impl Time {
pub fn new () -> Time {
return Time {
sntp: None
}
}
pub fn sync(&mut self) -> anyhow::Result<()> {
let sntp = sntp::EspSntp::new_default().map_err(|err| {
anyhow::anyhow!("ESP SNTP Failed: {:?}", err)
})?;
self.sntp = Some(Box::new(sntp));
return anyhow::Ok(())
}
pub fn get_time(&mut self) -> Duration {
if let Some(ref mut sntp) = self.sntp {
info!("ESP SNTP sync status {:?}", sntp.get_sync_status());
}
EspSystemTime {}.now()
}
}

219
src/voltage_detection.rs Normal file
View File

@ -0,0 +1,219 @@
use std::{
sync::{Arc, Mutex},
thread::{sleep, spawn},
time::Duration,
};
use embedded_hal::{adc::Channel, prelude::_embedded_hal_adc_OneShot};
use embedded_svc::event_bus::Postbox;
use esp_idf_hal::{
adc::{
config::{self},
Analog, Atten6dB, PoweredAdc, ADC1,
},
gpio::{Gpio1, Gpio2, Gpio3, Input},
};
use esp_idf_svc::eventloop::{
Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData,
EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource,
};
use esp_idf_sys::c_types;
use log::{debug, warn};
use serde::{Deserialize, Serialize};
use crate::time::Time;
pub static mut VOLTAGE_EVENTLOOP: Option<EspEventLoop<esp_idf_svc::eventloop::User<Background>>> =
None;
const ADAPTER_OFFSET: f32 = 12002f32 / 900f32;
const BATTERY_OFFSET: f32 = 12002f32 / 900f32;
const OUTPUT_OFFSET: f32 = 12002f32 / 900f32;
pub struct VoltageDetection {
pub worker: VoltageDetectionWorker,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct VoltageDetectionWorker {
pub adapter_voltage: u16,
pub battery_voltage: u16,
pub output_voltage: u16,
}
impl VoltageDetection {
pub fn new() -> anyhow::Result<Self> {
return anyhow::Ok(Self {
worker: VoltageDetectionWorker::new(),
});
}
pub fn watching(&mut self) -> anyhow::Result<()> {
match EspBackgroundEventLoop::new(&Default::default()) {
Ok(eventloop) => unsafe { VOLTAGE_EVENTLOOP = Some(eventloop) },
Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err),
}
let worker = Arc::new(Mutex::new(self.worker));
spawn(move || {
let handler = || -> anyhow::Result<()> {
let mut adapter_pin = unsafe { Gpio1::<Input>::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::<Input>::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::<Input>::new() }
.into_analog_atten_6db()
.map_err(|err| {
anyhow::anyhow!("Failed to set GPIO 3 as analog input. {}", err)
})?;
let mut adc = PoweredAdc::new(unsafe { ADC1::new() }, config::Config::new())?;
let mut worker = worker.lock().map_err(|err| {
anyhow::anyhow!("Lock VoltageDetection Worker Failed. {}", err)
})?;
let mut last_runing_at;
loop {
last_runing_at = Time::new().get_time();
match worker.read_once(
&mut adc,
&mut adapter_pin,
&mut battery_pin,
&mut output_pin,
) {
Ok(_) => debug!(
"Adapter: {},\tBattery: {},\t Output: {}",
worker.adapter_voltage, worker.battery_voltage, worker.output_voltage
),
Err(err) => warn!("Read Failed. {}", err),
}
if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } {
if let Err(err) = eventloop.post(
&mut VoltageDetectionWorker {
adapter_voltage: worker.adapter_voltage,
battery_voltage: worker.battery_voltage,
output_voltage: worker.output_voltage,
},
None,
) {
warn!("Post Result to Event Loop failed. {}", err);
}
} else {
warn!("EVENTLOOP IS NONE");
}
let mut delta = Time::new().get_time() - last_runing_at;
if delta >= Duration::from_millis(5000) {
delta = Duration::ZERO
}
sleep(Duration::from_millis(5000) - delta);
}
};
if let Err(err) = handler() {
warn!("init failed. {}", err)
}
});
return anyhow::Ok(());
}
}
impl VoltageDetectionWorker {
pub fn new() -> Self {
return Self {
adapter_voltage: 0,
battery_voltage: 0,
output_voltage: 0,
};
}
pub fn read_once(
&mut self,
adc: &mut PoweredAdc<ADC1>,
adapter_pin: &mut Gpio1<Atten6dB<ADC1>>,
battery_pin: &mut Gpio2<Atten6dB<ADC1>>,
output_pin: &mut Gpio3<Atten6dB<ADC1>>,
) -> anyhow::Result<()> {
match self.read_pin_once(adc, adapter_pin) {
Ok(voltage) => {
self.adapter_voltage = ((voltage as f32) * ADAPTER_OFFSET) as u16;
}
Err(err) => {
warn!("Adapter Voltage read failed: {:?}", err);
}
}
match self.read_pin_once(adc, battery_pin) {
Ok(voltage) => {
self.battery_voltage = ((voltage as f32) * BATTERY_OFFSET) as u16;
}
Err(err) => {
warn!("Adapter Voltage read failed: {:?}", err);
}
}
match self.read_pin_once(adc, output_pin) {
Ok(voltage) => {
self.output_voltage = ((voltage as f32) * OUTPUT_OFFSET) as u16;
}
Err(err) => {
warn!("Adapter Voltage read failed: {:?}", err);
}
}
return anyhow::Ok(());
}
pub fn read_pin_once<AN: Analog<ADC1>, PIN: Channel<AN, ID = u8>>(
&mut self,
adc: &mut PoweredAdc<ADC1>,
pin: &mut PIN,
) -> anyhow::Result<u16> {
let mut avg_voltage: u16 = 0;
for _ in 0..10 {
let voltage = adc.read(pin);
match voltage {
Ok(voltage) => {
avg_voltage += voltage;
}
Err(err) => {
anyhow::bail!("Adapter Voltage read failed: {:?}", err)
}
};
sleep(Duration::from_millis(100));
}
anyhow::Ok(avg_voltage / 10)
}
}
impl EspTypedEventSource for VoltageDetectionWorker {
fn source() -> *const c_types::c_char {
b"VOLTAGES\0".as_ptr() as *const _
}
}
impl EspTypedEventSerializer<VoltageDetectionWorker> for VoltageDetectionWorker {
fn serialize<R>(
event: &VoltageDetectionWorker,
f: impl for<'a> FnOnce(&'a EspEventPostData) -> R,
) -> R {
f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) })
}
}
impl EspTypedEventDeserializer<VoltageDetectionWorker> for VoltageDetectionWorker {
fn deserialize<R>(
data: &EspEventFetchData,
f: &mut impl for<'a> FnMut(&'a VoltageDetectionWorker) -> R,
) -> R {
f(unsafe { data.as_payload() })
}
}

View File

@ -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<EspWifi>,
auto_connect: bool,
connected: bool,
}
impl WiFi {
impl Internet {
pub fn new() -> Result<Self> {
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<EspNetifStack>,
sys_loop_stack: Arc<EspSysLoopStack>,
default_nvs: Arc<EspDefaultNvs>,
) -> Result<Box<EspWifi>> {
const SSID: &str = "Ivan Li";
fn connect_ap(
&mut self,
) -> Result<()> {
const SSID: &str = "Ivan";
const PASSWORD: &str = "ivanli.cc";
let mut wifi = Box::new(EspWifi::new(netif_stack, sys_loop_stack, default_nvs)?);
info!("Wifi created, about to scan");
let ap_infos = wifi.scan()?;
let wifi = self.wifi.as_mut();
// let ap_infos = wifi.scan()?;
info!("Wifi AP Count {}", ap_infos.len());
// info!("Wifi AP Count {}", ap_infos.len());
let ours = ap_infos.into_iter().find(|a| a.ssid == SSID);
// let ours = ap_infos.into_iter().find(|a| a.ssid == SSID);
let channel = if let Some(ours) = ours {
info!(
"Found configured access point {} on channel {}",
SSID, ours.channel
);
Some(ours.channel)
} else {
info!(
"Configured access point {} not found during scanning, will go with unknown channel",
SSID
);
None
};
// let channel = if let Some(ours) = ours {
// info!(
// "Found configured access point {} on channel {}",
// SSID, ours.channel
// );
// Some(ours.channel)
// } else {
// info!(
// "Configured access point {} not found during scanning, will go with unknown channel",
// SSID
// );
// None
// };
wifi.set_configuration(&Configuration::Mixed(
wifi.set_configuration(&Configuration::Client(
ClientConfiguration {
ssid: SSID.into(),
password: PASSWORD.into(),
channel,
..Default::default()
},
AccessPointConfiguration {
ssid: "aptest".into(),
channel: channel.unwrap_or(1),
channel: None,
..Default::default()
},
// AccessPointConfiguration {
// ssid: "aptest".into(),
// channel: channel.unwrap_or(1),
// ..Default::default()
// },
))?;
info!("Wifi configuration set, about to get status");
@ -85,11 +81,13 @@ impl WiFi {
let status = wifi.get_status();
info!("we have the wifi status");
if let Status(
ClientStatus::Started(ClientConnectionStatus::Connected(ClientIpStatus::Done(
ip_settings,
))),
ApStatus::Started(ApIpStatus::Done),
ApStatus::Stopped
) = status
{
info!("Wifi connected");
@ -99,22 +97,22 @@ impl WiFi {
bail!("Unexpected Wifi status: {:?}", status);
}
Ok(wifi)
Ok(())
}
fn ping(ip_settings: &ipv4::ClientSettings) -> Result<()> {
info!("About to do some pings for {:?}", ip_settings);
let ping_summary =
EspPing::default().ping(ip_settings.subnet.gateway, &Default::default())?;
if ping_summary.transmitted != ping_summary.received {
bail!(
"Pinging gateway {} resulted in timeouts",
ip_settings.subnet.gateway
);
pub fn ping(ip_settings: &ipv4::ClientSettings) -> Result<()> {
info!("About to do some pings for {:?}", ip_settings);
let ping_summary =
EspPing::default().ping(ip_settings.subnet.gateway, &Default::default())?;
if ping_summary.transmitted != ping_summary.received {
bail!(
"Pinging gateway {} resulted in timeouts",
ip_settings.subnet.gateway
);
}
info!("Pinging done");
Ok(())
}
info!("Pinging done");
Ok(())
}
}
}