esp32c3/src/main.rs
2024-05-05 22:13:18 +02:00

344 lines
11 KiB
Rust

//! embassy hello world
//!
//! This is an example of running the embassy executor with multiple tasks
//! concurrently.
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![feature(trait_alias)]
#![feature(generic_arg_infer)]
extern crate alloc;
use core::mem::{self, MaybeUninit};
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use embassy_executor::Executor;
use embassy_net::{Config, DhcpConfig, Stack, StackResources};
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Timer};
use esp_alloc::EspHeap;
use esp_backtrace as _;
use esp_hal::adc::{AdcPin, Attenuation, ADC};
use esp_hal::clock::CpuClock;
use esp_hal::gpio::{
Analog, AnalogPin, AnyPin, GpioPin, OpenDrain, Output, OutputSignal, PushPull,
};
use esp_hal::peripherals::{Peripherals, ADC1, ADC2, TIMG0};
use esp_hal::systimer::SystemTimer;
use esp_hal::{adc::AdcConfig, clock::ClockControl};
use esp_hal::{embassy, prelude::*, rmt, Rmt, Rng, Rtc, IO};
use esp_hal_smartled::SmartLedsAdapter;
use esp_hal_smartled::*;
use esp_println::println;
use esp_wifi::wifi::{get_random, ClientConfiguration, Configuration};
use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiStaDevice, WifiState};
use esp_wifi::{initialize as initialize_wifi, EspWifiInitFor};
use smart_leds::{SmartLedsWrite, RGB8};
use static_cell::{make_static, StaticCell};
use crate::mqtt::publish_data;
mod mqtt;
#[global_allocator]
static ALLOCATOR: EspHeap = EspHeap::empty();
const SSID: Option<&str> = option_env!("WIFI_SSID");
const PASSWORD: Option<&str> = option_env!("WIFI_PASSWORD");
pub type NetworkStack = &'static Stack<WifiDevice<'static, WifiStaDevice>>;
fn init_heap() {
const HEAP_SIZE: usize = 32 * 1024;
static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit();
unsafe {
ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE);
}
}
trait MySmartLed {
fn set_color(&mut self, color: RGB8);
}
impl<TX: rmt::TxChannel, const BUFFER_SIZE: usize> MySmartLed
for SmartLedsAdapter<TX, BUFFER_SIZE>
{
fn set_color(&mut self, color: RGB8) {
if let Err(err) = self.write([color].into_iter()) {
esp_println::println!("{err:?}");
}
}
}
pub static DATA: Mutex<CriticalSectionRawMutex, BTreeMap<Cow<'static, str>, String>> =
Mutex::new(BTreeMap::new());
#[embassy_executor::task]
async fn blink(mut led: Box<dyn MySmartLed>) {
loop {
esp_println::println!("Bing!");
let scale = 4;
for r in 0..(255 / scale) {
Timer::after(Duration::from_millis(1)).await;
for g in 0..(255 / scale) {
Timer::after(Duration::from_millis(1)).await;
for b in 0..(255 / scale) {
Timer::after(Duration::from_millis(1)).await;
let color = RGB8::new(
(r * scale % 255) as _,
(g * scale % 255) as _,
(b * scale % 255) as _,
);
led.set_color(color);
}
}
}
}
}
async fn async_read<T, E>(
mut fun: impl FnMut() -> nb::Result<T, E>,
interval: Duration,
) -> Result<T, E> {
loop {
match fun() {
Err(nb::Error::WouldBlock) => Timer::after(interval).await,
Err(nb::Error::Other(e)) => return Err(e),
Ok(val) => return Ok(val),
}
}
}
/// https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32c3/api-reference/peripherals/adc.html
fn adc_voltage(d_out: u16, att: Attenuation) -> u16 {
let scale = match att {
Attenuation::Attenuation0dB => 800,
Attenuation::Attenuation6dB => 1350,
Attenuation::Attenuation2p5dB => 1100,
Attenuation::Attenuation11dB => 2600,
};
(d_out as u32 * scale / 4095) as u16
}
#[embassy_executor::task]
async fn moisture(
mut pin: AdcPin<GpioPin<Analog, 4>, ADC1>,
adc: &'static Mutex<NoopRawMutex, ADC<'static, ADC1>>,
mut supply: AnyPin<Output<PushPull>>,
) {
let warmup = Duration::from_millis(100);
let submerged_in_water = 1500;
let dry = 2600;
loop {
supply.set_high().unwrap();
Timer::after(warmup).await;
let pin_value: u16 = {
let mut adc1 = adc.lock().await;
async_read(|| adc1.read(&mut pin), Duration::from_millis(10)).await
}
.unwrap();
supply.set_low();
// Vout = Dout * Vmax / Dmax
let milli_volt = adc_voltage(pin_value, Attenuation::Attenuation11dB) as u32;
let moisture = ((milli_volt.checked_sub(submerged_in_water).unwrap_or(0))
<< 8 / (dry - submerged_in_water))
>> 8;
esp_println::println!("moisture: {moisture}%, v_s: {milli_volt}");
{
DATA.lock()
.await
.insert(Cow::from("moisture"), moisture.to_string());
}
Timer::after(Duration::from_secs(10) - warmup).await;
}
}
#[embassy_executor::task]
async fn battery_monitor(
mut vbat_in: AdcPin<GpioPin<Analog, 3>, ADC1>,
adc: &'static Mutex<NoopRawMutex, ADC<'static, ADC1>>,
) {
loop {
let v_out = adc_voltage(
{
let mut adc1 = adc.lock().await;
async_read(|| adc1.read(&mut vbat_in), Duration::from_millis(10)).await
}
.unwrap(),
Attenuation::Attenuation11dB,
);
// account for 50:50 voltage divider
let v_bat = v_out * 2;
println!("V_bat: {}", v_bat);
{
DATA.lock()
.await
.insert(Cow::from("vbat"), v_bat.to_string());
}
Timer::after(Duration::from_secs(15)).await;
}
}
static EXECUTOR: StaticCell<Executor> = StaticCell::new();
#[entry]
fn main() -> ! {
esp_println::println!("Init!");
init_heap();
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock160MHz).freeze();
let mut rtc = Rtc::new(peripherals.LPWR);
let mut timer_group0 = esp_hal::timer::TimerGroup::new(peripherals.TIMG0, &clocks);
let timer_group1 = esp_hal::timer::TimerGroup::new(peripherals.TIMG1, &clocks);
{
let mut wdt0 = &mut timer_group0.wdt;
let mut wdt1 = timer_group1.wdt;
// Disable watchdog timers
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
}
let timer = SystemTimer::new(peripherals.SYSTIMER).alarm0;
let mut rng = Rng::new(peripherals.RNG);
let mut seed = [0u8; 32]; // very random, very secure seed
rng.read(&mut seed).expect("random seed");
let wifi_init = initialize_wifi(
EspWifiInitFor::Wifi,
timer,
rng,
system.radio_clock_control,
&clocks,
)
.unwrap();
let wifi = peripherals.WIFI;
let (wifi_interface, controller) =
esp_wifi::wifi::new_with_mode(&wifi_init, wifi, WifiStaDevice).unwrap();
let mut hostname = heapless::String::<_>::new();
hostname.push_str("esp32c3");
let config = Config::dhcpv4({
let mut dhcp = DhcpConfig::default();
dhcp.hostname = Some(hostname);
dhcp
});
// Init network stack
let stack = &*make_static!(Stack::new(
wifi_interface,
config,
make_static!(StackResources::<3>::new()),
u64::from_le_bytes(seed[..8].try_into().unwrap())
));
embassy::init(&clocks, timer_group0);
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let rmt = Rmt::new(peripherals.RMT, 80u32.MHz(), &clocks).unwrap();
let rmt_buffer = smartLedBuffer!(1);
let mut led = SmartLedsAdapter::new(rmt.channel0, io.pins.gpio7, rmt_buffer, &clocks);
led.set_color(RGB8::new(0, 255, 255));
let led: Box<dyn MySmartLed> = Box::new(led);
let mut adc1_config = AdcConfig::new();
let pin = adc1_config.enable_pin(io.pins.gpio4.into_analog(), Attenuation::Attenuation11dB);
let vbat_in = adc1_config.enable_pin(io.pins.gpio3.into_analog(), Attenuation::Attenuation11dB);
let moisture_sensor_suppy_pin = io.pins.gpio5.into_push_pull_output().degrade();
let adc1: &'static Mutex<NoopRawMutex, ADC<'static, ADC1>> =
make_static!(Mutex::new(ADC::<ADC1>::new(peripherals.ADC1, adc1_config)));
let executor = EXECUTOR.init(Executor::new());
executor.run(move |spawner| {
if let (Some(ssid), Some(psk)) = (SSID.as_ref(), PASSWORD.as_ref()) {
spawner
.spawn(wifi_connection(controller, ssid, psk))
.unwrap();
spawner.spawn(net_task(&stack)).unwrap();
spawner.spawn(ip_task(&stack)).unwrap();
}
spawner.spawn(blink(led)).unwrap();
spawner
.spawn(moisture(pin, adc1, moisture_sensor_suppy_pin.into()))
.unwrap();
spawner.spawn(battery_monitor(vbat_in, adc1)).unwrap();
spawner.spawn(publish_data(&stack, seed, Duration::from_secs(60)));
})
}
#[embassy_executor::task]
async fn wifi_connection(
mut controller: WifiController<'static>,
ssid: &'static str,
psk: &'static str,
) {
println!("start connection task");
println!("Device capabilities: {:?}", controller.get_capabilities());
loop {
match esp_wifi::wifi::get_wifi_state() {
WifiState::StaConnected => {
// wait until we're no longer connected
controller.wait_for_event(WifiEvent::StaDisconnected).await;
Timer::after(Duration::from_millis(5000)).await
}
_ => {}
}
if !matches!(controller.is_started(), Ok(true)) {
let client_config = Configuration::Client(ClientConfiguration {
ssid: ssid.try_into().unwrap(),
password: psk.try_into().unwrap(),
..Default::default()
});
controller.set_configuration(&client_config).unwrap();
println!("Starting wifi");
controller.start().await.unwrap();
println!("Wifi started!");
}
println!("About to connect...");
match controller.connect().await {
Ok(_) => println!("Wifi connected!"),
Err(e) => {
println!("Failed to connect to wifi: {e:?}");
Timer::after(Duration::from_millis(5000)).await
}
}
}
}
#[embassy_executor::task]
async fn ip_task(stack: NetworkStack) {
loop {
if stack.is_link_up() {
break;
}
Timer::after(Duration::from_millis(500)).await;
}
println!("Waiting to get IP address...");
loop {
if let Some(config) = stack.config_v4() {
println!("Got IP: {}", config.address);
break;
}
Timer::after(Duration::from_millis(500)).await;
}
}
#[embassy_executor::task]
async fn net_task(stack: NetworkStack) {
stack.run().await
}